Completed
Pull Request — develop (#1708)
by Aristeides
15:56 queued 13:45
created

kirkiSetSettingValue.set   D

Complexity

Conditions 33
Paths 24

Size

Total Lines 135

Duplication

Lines 135
Ratio 100 %

Importance

Changes 0
Metric Value
cc 33
nc 24
nop 2
dl 135
loc 135
rs 4.3465
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like kirkiSetSettingValue.set often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/* jshint -W079 */
2
/* jshint unused:false */
3
if ( _.isUndefined( window.kirkiSetSettingValue ) ) {
4
	var kirkiSetSettingValue = { // jscs:ignore requireVarDeclFirst
5
6
		/**
7
		 * Set the value of the control.
8
		 *
9
		 * @since 3.0.0
10
		 * @param string setting The setting-ID.
11
		 * @param mixed  value   The value.
12
		 */
13
		set: function( setting, value ) {
14
15
			/**
16
			 * Get the control of the sub-setting.
17
			 * This will be used to get properties we need from that control,
18
			 * and determine if we need to do any further work based on those.
19
			 */
20
			var $this = this,
21
			    subControl = wp.customize.settings.controls[ setting ],
22
			    valueJSON;
23
24
			// If the control doesn't exist then return.
25
			if ( _.isUndefined( subControl ) ) {
26
				return true;
27
			}
28
29
			// First set the value in the wp object. The control type doesn't matter here.
30
			$this.setValue( setting, value );
31
32
			// Process visually changing the value based on the control type.
33
			switch ( subControl.type ) {
34
35
				case 'kirki-background':
36
					if ( ! _.isUndefined( value['background-color'] ) ) {
37
						$this.setColorPicker( $this.findElement( setting, '.kirki-color-control' ), value['background-color'] );
38
					}
39
					$this.findElement( setting, '.placeholder, .thumbnail' ).removeClass().addClass( 'placeholder' ).html( 'No file selected' );
40
					_.each( ['background-repeat', 'background-position'], function( subVal ) {
41
						if ( ! _.isUndefined( value[ subVal ] ) ) {
42
							$this.setSelectWoo( $this.findElement( setting, '.' + subVal + ' select' ), value[ subVal ] );
43
						}
44
					});
45
					_.each( ['background-size', 'background-attachment'], function( subVal ) {
46
						jQuery( $this.findElement( setting, '.' + subVal + ' input[value="' + value + '"]' ) ).prop( 'checked', true );
47
					});
48
					valueJSON = JSON.stringify( value ).replace( /'/g, '&#39' );
49
					jQuery( $this.findElement( setting, '.background-hidden-value' ).attr( 'value', valueJSON ) ).trigger( 'change' );
50
					break;
51
52
				case 'kirki-code':
53
					jQuery( $this.findElement( setting, '.CodeMirror' ) )[0].CodeMirror.setValue( value );
54
					break;
55
56
				case 'checkbox':
57
				case 'kirki-switch':
58
				case 'kirki-toggle':
59
					value = ( 1 === value || '1' === value || true === value ) ? true : false;
60
					jQuery( $this.findElement( setting, 'input' ) ).prop( 'checked', value );
61
					wp.customize.instance( setting ).set( value );
62
					break;
63
64
				case 'kirki-select':
65
				case 'kirki-preset':
66
				case 'kirki-fontawesome':
67
					$this.setSelectWoo( $this.findElement( setting, 'select' ), value );
68
					break;
69
70
				case 'kirki-slider':
71
					jQuery( $this.findElement( setting, 'input' ) ).prop( 'value', value );
72
					jQuery( $this.findElement( setting, '.kirki_range_value .value' ) ).html( value );
73
					break;
74
75
				case 'kirki-generic':
76
					if ( _.isUndefined( subControl.choices ) || _.isUndefined( subControl.choices.element ) ) {
77
						subControl.choices.element = 'input';
78
					}
79
					jQuery( $this.findElement( setting, subControl.choices.element ) ).prop( 'value', value );
80
					break;
81
82
				case 'kirki-color':
83
					$this.setColorPicker( $this.findElement( setting, '.kirki-color-control' ), value );
84
					break;
85
86
				case 'kirki-multicheck':
87
					$this.findElement( setting, 'input' ).each( function() {
88
						jQuery( this ).prop( 'checked', false );
89
					});
90
					_.each( value, function( subValue, i ) {
91
						jQuery( $this.findElement( setting, 'input[value="' + value[ i ] + '"]' ) ).prop( 'checked', true );
92
					});
93
					break;
94
95
				case 'kirki-multicolor':
96
					_.each( value, function( subVal, index ) {
97
						$this.setColorPicker( $this.findElement( setting, '.multicolor-index-' + index ), subVal );
98
					});
99
					break;
100
101
				case 'kirki-radio-buttonset':
102
				case 'kirki-radio-image':
103
				case 'kirki-radio':
104
				case 'kirki-dashicons':
105
				case 'kirki-color-palette':
106
				case 'kirki-palette':
107
					jQuery( $this.findElement( setting, 'input[value="' + value + '"]' ) ).prop( 'checked', true );
108
					break;
109
110
				case 'kirki-typography':
111
					_.each( ['font-family', 'variant', 'subsets'], function( subVal ) {
112
						if ( ! _.isUndefined( value[ subVal ] ) ) {
113
							$this.setSelectWoo( $this.findElement( setting, '.' + subVal + ' select' ), value[ subVal ] );
114
						}
115
					});
116
					_.each( ['font-size', 'line-height', 'letter-spacing', 'word-spacing'], function( subVal ) {
117
						if ( ! _.isUndefined( value[ subVal ] ) ) {
118
							jQuery( $this.findElement( setting, '.' + subVal + ' input' ) ).prop( 'value', value[ subVal ] );
119
						}
120
					});
121
122
					if ( ! _.isUndefined( value.color ) ) {
123
						$this.setColorPicker( $this.findElement( setting, '.kirki-color-control' ), value.color );
124
					}
125
					valueJSON = JSON.stringify( value ).replace( /'/g, '&#39' );
126
					jQuery( $this.findElement( setting, '.typography-hidden-value' ).attr( 'value', valueJSON ) ).trigger( 'change' );
127
					break;
128
129
				case 'kirki-dimensions':
130
					_.each( value, function( subValue, id ) {
131
						jQuery( $this.findElement( setting, '.' + id + ' input' ) ).prop( 'value', subValue );
132
					});
133
					break;
134
135
				case 'kirki-repeater':
136
137
					// Not yet implemented.
138
					break;
139
140
				case 'kirki-custom':
141
142
					// Do nothing.
143
					break;
144
				default:
145
					jQuery( $this.findElement( setting, 'input' ) ).prop( 'value', value );
146
			}
147
		},
148
149
		/**
150
		 * Set the value for colorpickers.
151
		 * CAUTION: This only sets the value visually, it does not change it in th wp object.
152
		 *
153
		 * @since 3.0.0
154
		 * @param object selector jQuery object for this element.
155
		 * @param string value    The value we want to set.
156
		 */
157
		setColorPicker: function( selector, value ) {
158
			selector.attr( 'data-default-color', value ).data( 'default-color', value ).wpColorPicker( 'color', value );
159
		},
160
161
		/**
162
		 * Sets the value in a selectWoo element.
163
		 * CAUTION: This only sets the value visually, it does not change it in th wp object.
164
		 *
165
		 * @since 3.0.0
166
		 * @param string selector The CSS identifier for this selectWoo.
167
		 * @param string value    The value we want to set.
168
		 */
169
		setSelectWoo: function( selector, value ) {
170
			jQuery( selector ).selectWoo().val( value ).trigger( 'change' );
171
		},
172
173
		/**
174
		 * Sets the value in textarea elements.
175
		 * CAUTION: This only sets the value visually, it does not change it in th wp object.
176
		 *
177
		 * @since 3.0.0
178
		 * @param string selector The CSS identifier for this textarea.
179
		 * @param string value    The value we want to set.
180
		 */
181
		setTextarea: function( selector, value ) {
182
			jQuery( selector ).prop( 'value', value );
183
		},
184
185
		/**
186
		 * Finds an element inside this control.
187
		 *
188
		 * @since 3.0.0
189
		 * @param string setting The setting ID.
190
		 * @param string element The CSS identifier.
191
		 */
192
		findElement: function( setting, element ) {
193
			return wp.customize.control( setting ).container.find( element );
194
		},
195
196
		/**
197
		 * Updates the value in the wp.customize object.
198
		 *
199
		 * @since 3.0.0
200
		 * @param string setting The setting-ID.
201
		 * @param mixed  value   The value.
202
		 */
203
		setValue: function( setting, value, timeout ) {
204
			timeout = ( _.isUndefined( timeout ) ) ? 100 : parseInt( timeout, 10 );
205
			wp.customize.instance( setting ).set({});
206
			setTimeout( function() {
207
				wp.customize.instance( setting ).set( value );
208
			}, timeout );
209
		}
210
	};
211
}
212
var kirki = {
213
214
	initialized: false,
215
216
	/**
217
	 * Initialize the object.
218
	 *
219
	 * @since 3.0.17
220
	 * @returns {null}
221
	 */
222
	initialize: function() {
223
		var self = this;
224
225
		// We only need to initialize once.
226
		if ( self.initialized ) {
227
			return;
228
		}
229
230
		setTimeout( function() {
231
			kirki.util.webfonts.standard.initialize();
232
			kirki.util.webfonts.google.initialize();
233
		}, 150 );
234
235
		// Mark as initialized.
236
		self.initialized = true;
237
	}
238
};
239
240
// Initialize the kirki object.
241
kirki.initialize();
242
var kirki = kirki || {};
243
kirki = jQuery.extend( kirki, {
244
245
	/**
246
	 * An object containing definitions for controls.
247
	 *
248
	 * @since 3.0.16
249
	 */
250
	control: {
251
252
		/**
253
		 * The radio control.
254
		 *
255
		 * @since 3.0.17
256
		 */
257
		'kirki-radio': {
258
259
			/**
260
			 * Init the control.
261
			 *
262
			 * @since 3.0.17
263
			 * @param {Object} control - The customizer control object.
264
			 * @returns {null}
265
			 */
266
			init: function( control ) {
267
				var self = this;
268
269
				// Render the template.
270
				self.template( control );
271
272
				// Init the control.
273
				kirki.input.radio.init( control );
274
275
			},
276
277
			/**
278
			 * Render the template.
279
			 *
280
			 * @since 3.0.17
281
			 * @param {Object} control - The customizer control object.
282
			 * @param {Object} control.params - The control parameters.
283
			 * @param {string} control.params.label - The control label.
284
			 * @param {string} control.params.description - The control description.
285
			 * @param {string} control.params.inputAttrs - extra input arguments.
286
			 * @param {string} control.params.default - The default value.
287
			 * @param {Object} control.params.choices - Any extra choices we may need.
288
			 * @param {string} control.id - The setting.
289
			 * @returns {null}
290
			 */
291
			template: function( control ) {
292
				var template = wp.template( 'kirki-input-radio' );
293
				control.container.html( template( {
294
					label: control.params.label,
295
					description: control.params.description,
296
					'data-id': control.id,
297
					inputAttrs: control.params.inputAttrs,
298
					'default': control.params['default'],
299
					value: kirki.setting.get( control.id ),
300
					choices: control.params.choices
301
				} ) );
302
			}
303
		},
304
305
		/**
306
		 * The color control.
307
		 *
308
		 * @since 3.0.16
309
		 */
310
		'kirki-color': {
311
312
			/**
313
			 * Init the control.
314
			 *
315
			 * @since 3.0.16
316
			 * @param {Object} control - The customizer control object.
317
			 * @returns {null}
318
			 */
319
			init: function( control ) {
320
				var self = this;
321
322
				// Render the template.
323
				self.template( control );
324
325
				// Init the control.
326
				kirki.input.color.init( control );
327
328
			},
329
330
			/**
331
			 * Render the template.
332
			 *
333
			 * @since 3.0.16
334
			 * @param {Object}     control - The customizer control object.
335
			 * @param {Object}     control.params - The control parameters.
336
			 * @param {string}     control.params.label - The control label.
337
			 * @param {string}     control.params.description - The control description.
338
			 * @param {string}     control.params.mode - The colorpicker mode. Can be 'full' or 'hue'.
339
			 * @param {bool|array} control.params.palette - false if we don't want a palette,
340
			 *                                              true to use the default palette,
341
			 *                                              array of custom hex colors if we want a custom palette.
342
			 * @param {string}     control.params.inputAttrs - extra input arguments.
343
			 * @param {string}     control.params.default - The default value.
344
			 * @param {Object}     control.params.choices - Any extra choices we may need.
345
			 * @param {boolean}    control.params.choices.alpha - should we add an alpha channel?
346
			 * @param {string}     control.id - The setting.
347
			 * @returns {null}
348
			 */
349
			template: function( control ) {
350
				var template = wp.template( 'kirki-input-color' );
351
				control.container.html( template( {
352
					label: control.params.label,
353
					description: control.params.description,
354
					'data-id': control.id,
355
					mode: control.params.mode,
356
					inputAttrs: control.params.inputAttrs,
357
					'data-palette': control.params.palette,
358
					'data-default-color': control.params['default'],
359
					'data-alpha': control.params.choices.alpha,
360
					value: kirki.setting.get( control.id )
361
				} ) );
362
			}
363
		},
364
365
		/**
366
		 * The generic control.
367
		 *
368
		 * @since 3.0.16
369
		 */
370
		'kirki-generic': {
371
372
			/**
373
			 * Init the control.
374
			 *
375
			 * @since 3.0.17
376
			 * @param {Object} control - The customizer control object.
377
			 * @param {Object} control.params - Control parameters.
378
			 * @param {Object} control.params.choices - Define the specifics for this input.
379
			 * @param {string} control.params.choices.element - The HTML element we want to use ('input', 'div', 'span' etc).
380
			 * @returns {null}
381
			 */
382
			init: function( control ) {
383
				var self = this;
384
385
				// Render the template.
386
				self.template( control );
387
388
				// Init the control.
389
				if ( ! _.isUndefined( control.params ) && ! _.isUndefined( control.params.choices ) && ! _.isUndefined( control.params.choices.element ) && 'textarea' === control.params.choices.element ) {
390
					kirki.input.textarea.init( control );
391
					return;
392
				}
393
				kirki.input.genericInput.init( control );
394
			},
395
396
			/**
397
			 * Render the template.
398
			 *
399
			 * @since 3.0.17
400
			 * @param {Object}  control - The customizer control object.
401
			 * @param {Object}  control.params - The control parameters.
402
			 * @param {string}  control.params.label - The control label.
403
			 * @param {string}  control.params.description - The control description.
404
			 * @param {string}  control.params.inputAttrs - extra input arguments.
405
			 * @param {string}  control.params.default - The default value.
406
			 * @param {Object}  control.params.choices - Any extra choices we may need.
407
			 * @param {boolean} control.params.choices.alpha - should we add an alpha channel?
408
			 * @param {string}  control.id - The setting.
409
			 * @returns {null}
410
			 */
411
			template: function( control ) {
412
				var args = {
413
						label: control.params.label,
414
						description: control.params.description,
415
						'data-id': control.id,
416
						inputAttrs: control.params.inputAttrs,
417
						choices: control.params.choices,
418
						value: kirki.setting.get( control.id )
419
				    },
420
				    template;
421
422
				if ( ! _.isUndefined( control.params ) && ! _.isUndefined( control.params.choices ) && ! _.isUndefined( control.params.choices.element ) && 'textarea' === control.params.choices.element ) {
423
					template = wp.template( 'kirki-input-textarea' );
424
					control.container.html( template( args ) );
425
					return;
426
				}
427
				template = wp.template( 'kirki-input-generic' );
428
				control.container.html( template( args ) );
429
			}
430
		},
431
432
		'kirki-select': {
433
434
			/**
435
			 * Init the control.
436
			 *
437
			 * @since 3.0.17
438
			 * @param {Object} control - The customizer control object.
439
			 * @returns {null}
440
			 */
441
			init: function( control ) {
442
				var self = this;
443
444
				// Render the template.
445
				self.template( control );
446
447
				// Init the control.
448
				kirki.input.select.init( control );
449
			},
450
451
			/**
452
			 * Render the template.
453
			 *
454
			 * @since 3.0.17
455
			 * @param {Object}  control - The customizer control object.
456
			 * @param {Object}  control.params - The control parameters.
457
			 * @param {string}  control.params.label - The control label.
458
			 * @param {string}  control.params.description - The control description.
459
			 * @param {string}  control.params.inputAttrs - extra input arguments.
460
			 * @param {Object}  control.params.default - The default value.
461
			 * @param {Object}  control.params.choices - The choices for the select dropdown.
462
			 * @param {string}  control.id - The setting.
463
			 * @returns {null}
464
			 */
465
			template: function( control ) {
466
				var template = wp.template( 'kirki-input-select' );
467
468
				control.container.html( template( {
469
					label: control.params.label,
470
					description: control.params.description,
471
					'data-id': control.id,
472
					inputAttrs: control.params.inputAttrs,
473
					choices: control.params.choices,
474
					value: kirki.setting.get( control.id ),
475
					multiple: control.params.multiple || 1,
476
					placeholder: control.params.placeholder
477
			    } ) );
478
			}
479
		}
480
	}
481
} );
482
/* global kirkiL10n */
483
var kirki = kirki || {};
484
kirki = jQuery.extend( kirki, {
485
	/**
486
	 * An object containing definitions for input fields.
487
	 *
488
	 * @since 3.0.16
489
	 */
490
	input: {
491
492
		/**
493
		 * Radio input fields.
494
		 *
495
		 * @since 3.0.17
496
		 */
497
		radio: {
498
499
			/**
500
			 * Init the control.
501
			 *
502
			 * @since 3.0.17
503
			 * @param {Object} control - The control object.
504
			 * @param {Object} control.id - The setting.
505
			 * @returns {null}
506
			 */
507
			init: function( control ) {
508
				var input = jQuery( 'input[data-id="' + control.id + '"]' );
509
510
				// Save the value
511
				input.on( 'change keyup paste click', function() {
512
					kirki.setting.set( control.id, jQuery( this ).val() );
513
				});
514
			}
515
		},
516
517
		/**
518
		 * Color input fields.
519
		 *
520
		 * @since 3.0.16
521
		 */
522
		color: {
523
524
			/**
525
			 * Init the control.
526
			 *
527
			 * @since 3.0.16
528
			 * @param {Object} control - The control object.
529
			 * @param {Object} control.id - The setting.
530
			 * @param {Object} control.choices - Additional options for the colorpickers.
531
			 * @param {Object} control.params - Control parameters.
532
			 * @param {Object} control.params.choices - alias for control.choices.
533
534
			 * @returns {null}
535
			 */
536
			init: function( control ) {
537
				var picker = jQuery( '.kirki-color-control[data-id="' + control.id + '"]' ),
538
				    clear;
539
540
				control.choices = control.choices || {};
541
				if ( _.isEmpty( control.choices ) && control.params.choices ) {
542
					control.choices = control.params.choices;
543
				}
544
545
				// If we have defined any extra choices, make sure they are passed-on to Iris.
546
				if ( ! _.isEmpty( control.choices ) ) {
547
					picker.wpColorPicker( control.choices );
548
				}
549
550
				// Tweaks to make the "clear" buttons work.
551
				setTimeout( function() {
552
					clear = jQuery( '.kirki-input-container[data-id="' + control.id + '"] .wp-picker-clear' );
553
					if ( clear.length ) {
554
						clear.click( function() {
555
							kirki.setting.set( control.id, '' );
556
						});
557
					}
558
				}, 200 );
559
560
				// Saves our settings to the WP API
561
				picker.wpColorPicker({
562
					change: function() {
563
564
						// Small hack: the picker needs a small delay
565
						setTimeout( function() {
566
							kirki.setting.set( control.id, picker.val() );
567
						}, 20 );
568
					}
569
				});
570
			}
571
		},
572
573
		/**
574
		 * Generic input fields.
575
		 *
576
		 * @since 3.0.17
577
		 */
578
		genericInput: {
579
580
			/**
581
			 * Init the control.
582
			 *
583
			 * @since 3.0.17
584
			 * @param {Object} control - The control object.
585
			 * @param {Object} control.id - The setting.
586
			 * @returns {null}
587
			 */
588
			init: function( control ) {
589
				var input = jQuery( 'input[data-id="' + control.id + '"]' );
590
591
				// Save the value
592
				input.on( 'change keyup paste click', function() {
593
					kirki.setting.set( control.id, jQuery( this ).val() );
594
				});
595
			}
596
		},
597
598
		/**
599
		 * Generic input fields.
600
		 *
601
		 * @since 3.0.17
602
		 */
603
		textarea: {
604
605
			/**
606
			 * Init the control.
607
			 *
608
			 * @since 3.0.17
609
			 * @param {Object} control - The control object.
610
			 * @param {Object} control.id - The setting.
611
			 * @returns {null}
612
			 */
613
			init: function( control ) {
614
				var textarea = jQuery( 'textarea[data-id="' + control.id + '"]' );
615
616
				// Save the value
617
				textarea.on( 'change keyup paste click', function() {
618
					kirki.setting.set( control.id, jQuery( this ).val() );
619
				});
620
			}
621
		},
622
623
		select: {
624
625
			/**
626
			 * Init the control.
627
			 *
628
			 * @since 3.0.17
629
			 * @param {Object} control - The control object.
630
			 * @param {Object} control.id - The setting.
631
			 * @returns {null}
632
			 */
633
			init: function( control ) {
634
				var element  = jQuery( 'select[data-id="' + control.id + '"]' ),
635
				    multiple = parseInt( element.data( 'multiple' ), 10 ),
636
				    selectValue,
637
				    selectWooOptions = {
638
						escapeMarkup: function( markup ) {
639
							return markup;
640
						}
641
				    };
642
					if ( control.params.placeholder ) {
643
						selectWooOptions.placeholder = control.params.placeholder;
644
						selectWooOptions.allowClear = true;
645
					}
646
647
				if ( 1 < multiple ) {
648
					selectWooOptions.maximumSelectionLength = multiple;
649
				}
650
				jQuery( element ).selectWoo( selectWooOptions ).on( 'change', function() {
651
					selectValue = jQuery( this ).val();
652
					selectValue = ( null === selectValue && 1 < multiple ) ? [] : selectValue;
653
					kirki.setting.set( control.id, selectValue );
654
				});
655
			}
656
		},
657
658
		image: {
659
660
			/**
661
			 * Get the HTML for image inputs.
662
			 *
663
			 * @since 3.0.17
664
			 * @param {Object} data - The arguments.
665
			 * @returns {string}
666
			 */
667
			getTemplate: function( data ) {
668
				var html   = '',
669
				    saveAs = 'url',
670
				    url;
671
672
				data = _.defaults( data, {
673
					label: '',
674
					description: '',
675
					inputAttrs: '',
676
					'data-id': '',
677
					choices: {},
678
					value: ''
679
				} );
680
681
				if ( ! _.isUndefined( data.choices ) && ! _.isUndefined( data.choices.save_as ) ) {
682
					saveAs = data.choices.save_as;
683
				}
684
				url = data.value;
685
				if ( _.isObject( data.value ) && ! _.isUndefined( data.value.url ) ) {
686
					url = data.value.url;
687
				}
688
689
				html += '<label>';
690
				if ( data.label ) {
691
					html += '<span class="customize-control-title">' + data.label + '</span>';
692
				}
693
				if ( data.description ) {
694
					html += '<span class="description customize-control-description">' + data.description + '</span>';
695
				}
696
				html += '</label>';
697
				html += '<div class="image-wrapper attachment-media-view image-upload">';
698
				if ( data.value.url || '' !== url ) {
699
					html += '<div class="thumbnail thumbnail-image"><img src="' + url + '" alt="" /></div>';
700
				} else {
701
					html += '<div class="placeholder">' + kirkiL10n.noFileSelected + '</div>';
702
				}
703
				html += '<div class="actions">';
704
				html += '<button class="button image-upload-remove-button' + ( '' === url ? ' hidden' : '' ) + '">' + kirkiL10n.remove + '</button>';
705
				if ( data['default'] && '' !== data['default'] ) {
706
					html += '<button type="button" class="button image-default-button"';
707
					if ( data['default'] === data.value || ( ! _.isUndefined( data.value.url ) && data['default'] === data.value.url ) ) {
708
						html += ' style="display:none;"';
709
					}
710
					html += '>' + kirkiL10n['default'] + '</button>';
711
				}
712
				html += '<button type="button" class="button image-upload-button">' + kirkiL10n.selectFile + '</button>';
713
				html += '</div></div>';
714
715
				return '<div class="kirki-input-container" data-id="' + data.id + '">' + html + '</div>';
716
			},
717
718
			/**
719
			 * Init the control.
720
			 *
721
			 * @since 3.0.17
722
			 * @param {Object} control - The control object.
723
			 * @returns {null}
724
			 */
725
			init: function( control ) { // jshint ignore:line
726
			}
727
		}
728
	}
729
} );
730
var kirki = kirki || {};
731
kirki = jQuery.extend( kirki, {
732
	/**
733
	 * An object containing definitions for settings.
734
	 *
735
	 * @since 3.0.16
736
	 */
737
	setting: {
738
739
		/**
740
		 * Gets the value of a setting.
741
		 *
742
		 * This is a helper function that allows us to get the value of
743
		 * control[key1][key2] for example, when the setting used in the
744
		 * customizer API is "control".
745
		 *
746
		 * @since 3.0.16
747
		 * @param {string} setting - The setting for which we're getting the value.
748
		 * @returns {mixed} Depends on the value.
749
		 */
750
		get: function( setting ) {
751
			var parts        = setting.split( '[' ),
752
			    foundSetting = '',
753
			    foundInStep  = 0,
754
			    currentVal   = '';
755
756
			_.each( parts, function( part, i ) {
757
				part = part.replace( ']', '' );
758
759
				if ( 0 === i ) {
760
					foundSetting = part;
761
				} else {
762
					foundSetting += '[' + part + ']';
763
				}
764
765
				if ( ! _.isUndefined( wp.customize.instance( foundSetting ) ) ) {
766
					currentVal  = wp.customize.instance( foundSetting ).get();
767
					foundInStep = i;
768
				}
769
770
				if ( foundInStep < i ) {
771
					if ( _.isObject( currentVal ) && ! _.isUndefined( currentVal[ part ] ) ) {
772
						currentVal = currentVal[ part ];
773
					}
774
				}
775
			});
776
777
			return currentVal;
778
		},
779
780
		/**
781
		 * Sets the value of a setting.
782
		 *
783
		 * This function is a bit complicated because there any many scenarios to consider.
784
		 * Example: We want to save the value for my_setting[something][3][something-else].
785
		 * The control's setting is my_setting[something].
786
		 * So we need to find that first, then figure out the remaining parts,
787
		 * merge the values recursively to avoid destroying my_setting[something][2]
788
		 * and also take into account any defined "key" arguments which take this even deeper.
789
		 *
790
		 * @since 3.0.16
791
		 * @param {object|string} element - The DOM element whose value has changed,
792
		 *                                  or an ID.
793
		 * @param {mixed}         value - Depends on the control-type.
794
		 * @param {string}        key - If we only want to save an item in an object
795
		 *                                  we can define the key here.
796
		 * @returns {null}
797
		 */
798
		set: function( element, value, key ) {
799
			var setting,
800
			    parts,
801
			    currentNode   = '',
802
			    foundNode     = '',
803
			    subSettingObj = {},
804
			    currentVal,
805
			    subSetting,
806
			    subSettingParts;
807
808
			// Get the setting from the element.
809
			setting = element;
810
			if ( _.isObject( element ) ) {
811
				if ( jQuery( element ).attr( 'data-id' ) ) {
812
					setting = element.attr( 'data-id' );
813
				} else {
814
					setting = element.parents( '[data-id]' ).attr( 'data-id' );
815
				}
816
			}
817
818
			if ( 'undefined' !== typeof wp.customize.control( setting ) ) {
819
				wp.customize.control( setting ).setting.set( value );
820
				return;
821
			}
822
823
			parts = setting.split( '[' ),
824
825
			// Find the setting we're using in the control using the customizer API.
826
			_.each( parts, function( part, i ) {
827
				part = part.replace( ']', '' );
828
829
				// The current part of the setting.
830
				currentNode = ( 0 === i ) ? part : '[' + part + ']';
831
832
				// When we find the node, get the value from it.
833
				// In case of an object we'll need to merge with current values.
834
				if ( ! _.isUndefined( wp.customize.instance( currentNode ) ) ) {
835
					foundNode  = currentNode;
836
					currentVal = wp.customize.instance( foundNode ).get();
837
				}
838
			} );
839
840
			// Get the remaining part of the setting that was unused.
841
			subSetting = setting.replace( foundNode, '' );
842
843
			// If subSetting is not empty, then we're dealing with an object
844
			// and we need to dig deeper and recursively merge the values.
845
			if ( '' !== subSetting ) {
846
				if ( ! _.isObject( currentVal ) ) {
847
					currentVal = {};
848
				}
849
				if ( '[' === subSetting.charAt( 0 ) ) {
850
					subSetting = subSetting.replace( '[', '' );
851
				}
852
				subSettingParts = subSetting.split( '[' );
853
				_.each( subSettingParts, function( subSettingPart, i ) {
854
					subSettingParts[ i ] = subSettingPart.replace( ']', '' );
855
				} );
856
857
				// If using a key, we need to go 1 level deeper.
858
				if ( key ) {
859
					subSettingParts.push( key );
860
				}
861
862
				// Converting to a JSON string and then parsing that to an object
863
				// may seem a bit hacky and crude but it's efficient and works.
864
				subSettingObj = '{"' + subSettingParts.join( '":{"' ) + '":"' + value + '"' + '}'.repeat( subSettingParts.length );
865
				subSettingObj = JSON.parse( subSettingObj );
866
867
				// Recursively merge with current value.
868
				jQuery.extend( true, currentVal, subSettingObj );
869
				value = currentVal;
870
871
			} else {
872
				if ( key ) {
873
					currentVal = ( ! _.isObject( currentVal ) ) ? {} : currentVal;
874
					currentVal[ key ] = value;
875
					value = currentVal;
876
				}
877
			}
878
			wp.customize.control( foundNode ).setting.set( value );
879
		}
880
	}
881
} );
882
/* global ajaxurl */
883
var kirki = kirki || {};
884
kirki = jQuery.extend( kirki, {
885
	/**
886
	 * A collection of utility methods.
887
	 *
888
	 * @since 3.0.17
889
	 */
890
	util: {
891
892
		/**
893
		 * A collection of utility methods for webfonts.
894
		 *
895
		 * @since 3.0.17
896
		 */
897
		webfonts: {
898
899
			/**
900
			 * Google-fonts related methods.
901
			 *
902
			 * @since 3.0.17
903
			 */
904
			google: {
905
906
				/**
907
				 * An object containing all Google fonts.
908
				 *
909
				 * to set this call this.setFonts();
910
				 *
911
				 * @since 3.0.17
912
				 */
913
				fonts: {},
914
915
				/**
916
				 * Init for google-fonts.
917
				 *
918
				 * @since 3.0.17
919
				 * @returns {null}
920
				 */
921
				initialize: function() {
922
					var self = this;
923
924
					self.setFonts();
925
				},
926
927
				/**
928
				 * Set fonts in this.fonts
929
				 *
930
				 * @since 3.0.17
931
				 * @returns {null}
932
				 */
933
				setFonts: function() {
934
					var self = this;
935
936
					// No need to run if we already have the fonts.
937
					if ( ! _.isEmpty( self.fonts ) ) {
938
						return;
939
					}
940
941
					// Make an AJAX call to set the fonts object (alpha).
942
					jQuery.post( ajaxurl, { 'action': 'kirki_fonts_google_all_get' }, function( response ) {
943
944
						// Get fonts from the JSON array.
945
						self.fonts = JSON.parse( response );
946
					} );
947
				},
948
949
				/**
950
				 * Gets all properties of a font-family.
951
				 *
952
				 * @since 3.0.17
953
				 * @param {string} family - The font-family we're interested in.
954
				 * @returns {Object}
955
				 */
956
				getFont: function( family ) {
957
					var self = this,
958
					    fonts = self.getFonts();
959
960
					if ( 'undefined' === typeof fonts[ family ] ) {
961
						return false;
962
					}
963
					return fonts[ family ];
964
				},
965
966
				/**
967
				 * Gets all properties of a font-family.
968
				 *
969
				 * @since 3.0.17
970
				 * @param {string} order - How to order the fonts (alpha|popularity|trending).
971
				 * @param {int}    number - How many to get. 0 for all.
972
				 * @returns {Object}
973
				 */
974
				getFonts: function( order, number ) {
975
					var self    = this,
976
					    ordered = {},
977
					    partial = [];
978
979
					// Make sure order is correct.
980
					order  = order || 'alpha';
981
					order  = ( 'alpha' !== order && 'popularity' !== order && 'trending' !== order ) ? 'alpha' : order;
982
983
					// Make sure number is correct.
984
					number = number || 0;
985
					number = parseInt( number, 10 );
986
987
					if ( 'alpha' === order || 0 === number ) {
988
						ordered = self.fonts.items;
989
					} else {
990
						partial = _.first( self.fonts.order[ order ], number );
991
						_.each( partial, function( family ) {
992
							ordered[ family ] = self.fonts.items[ family ];
993
						} );
994
					}
995
996
					return ordered;
997
				},
998
999
				/**
1000
				 * Gets the variants for a font-family.
1001
				 *
1002
				 * @since 3.0.17
1003
				 * @param {string} family - The font-family we're interested in.
1004
				 * @returns {Array}
1005
				 */
1006
				getVariants: function( family ) {
1007
					var self = this,
1008
					    font = self.getFont( family );
1009
1010
					// Early exit if font was not found.
1011
					if ( ! font ) {
1012
						return false;
1013
					}
1014
1015
					// Early exit if font doesn't have variants.
1016
					if ( _.isUndefined( font.variants ) ) {
1017
						return false;
1018
					}
1019
1020
					// Return the variants.
1021
					return font.variants;
1022
				},
1023
1024
				/**
1025
				 * Get the subsets for a font-family.
1026
				 *
1027
				 * @since 3.0.17
1028
				 * @param {string} family - The font-family we're interested in.
1029
				 * @returns {Object}
1030
				 */
1031
				getSubsets: function( family ) {
1032
					var self = this,
1033
					    font = self.getFont( family );
1034
1035
					// Early exit if font was not found.
1036
					if ( ! font ) {
1037
						return false;
1038
					}
1039
1040
					// Early exit if font doesn't have subsets.
1041
					if ( _.isUndefined( font.subsets ) ) {
1042
						return false;
1043
					}
1044
1045
					// Return the variants.
1046
					return font.subsets;
1047
				}
1048
			},
1049
1050
			/**
1051
			 * Standard fonts related methods.
1052
			 *
1053
			 * @since 3.0.17
1054
			 */
1055
			standard: {
1056
1057
				/**
1058
				 * An object containing all Standard fonts.
1059
				 *
1060
				 * to set this call this.setFonts();
1061
				 *
1062
				 * @since 3.0.17
1063
				 */
1064
				fonts: {},
1065
1066
				/**
1067
				 * Init for google-fonts.
1068
				 *
1069
				 * @since 3.0.17
1070
				 * @returns {null}
1071
				 */
1072
				initialize: function() {
1073
					var self = this;
1074
1075
					self.setFonts();
1076
				},
1077
1078
				/**
1079
				 * Set fonts in this.fonts
1080
				 *
1081
				 * @since 3.0.17
1082
				 * @returns {null}
1083
				 */
1084
				setFonts: function() {
1085
					var self = this;
1086
1087
					// No need to run if we already have the fonts.
1088
					if ( ! _.isEmpty( self.fonts ) ) {
1089
						return;
1090
					}
1091
1092
					// Make an AJAX call to set the fonts object.
1093
					jQuery.post( ajaxurl, { 'action': 'kirki_fonts_standard_all_get' }, function( response ) {
1094
1095
						// Get fonts from the JSON array.
1096
						self.fonts = JSON.parse( response );
1097
					} );
1098
				},
1099
1100
				/**
1101
				 * Gets the variants for a font-family.
1102
				 *
1103
				 * @since 3.0.17
1104
				 * @returns {Array}
1105
				 */
1106
				getVariants: function( family ) { // jshint ignore: line
1107
					return ['regular', 'italic', '700', '700italic'];
1108
				}
1109
			},
1110
1111
			/**
1112
			 * Figure out what this font-family is (google/standard)
1113
			 *
1114
			 * @since 3.0.20
1115
			 * @param {string} family - The font-family.
1116
			 * @returns {string|false} - Returns string if found (google|standard)
1117
			 *                           and false in case the font-family is an arbitrary value
1118
			 *                           not found anywhere in our font definitions.
1119
			 */
1120
			getFontType: function( family ) {
1121
				var self = this;
1122
1123
				// Check for standard fonts first.
1124
				if (
1125
					'undefined' !== typeof self.standard.fonts[ family ] || (
1126
						'undefined' !== typeof self.standard.fonts.stack &&
1127
						'undefined' !== typeof self.standard.fonts.stack[ family ]
1128
					)
1129
				) {
1130
					return 'standard';
1131
				}
1132
1133
				// Check in googlefonts.
1134
				if ( 'undefined' !== typeof self.google.fonts.items[ family ] ) {
1135
					return 'google';
1136
				}
1137
				return false;
1138
			}
1139
		}
1140
	}
1141
} );
1142
/* global kirki */
1143
/**
1144
 * The majority of the code in this file
1145
 * is derived from the wp-customize-posts plugin
1146
 * and the work of @westonruter to whom I am very grateful.
1147
 *
1148
 * @see https://github.com/xwp/wp-customize-posts
1149
 */
1150
1151
( function() {
1152
	'use strict';
1153
1154
	/**
1155
	 * A dynamic color-alpha control.
1156
	 *
1157
	 * @class
1158
	 * @augments wp.customize.Control
1159
	 * @augments wp.customize.Class
1160
	 */
1161
	wp.customize.kirkiDynamicControl = wp.customize.Control.extend({
1162
1163
		initialize: function( id, options ) {
1164
			var control = this,
1165
			    args    = options || {};
1166
1167
			args.params = args.params || {};
1168
			if ( ! args.params.type ) {
1169
				args.params.type = 'kirki-generic';
1170
			}
1171
			if ( ! args.params.content ) {
1172
				args.params.content = jQuery( '<li></li>' );
1173
				args.params.content.attr( 'id', 'customize-control-' + id.replace( /]/g, '' ).replace( /\[/g, '-' ) );
1174
				args.params.content.attr( 'class', 'customize-control customize-control-' + args.params.type );
1175
			}
1176
1177
			control.propertyElements = [];
1178
			wp.customize.Control.prototype.initialize.call( control, id, args );
1179
		},
1180
1181
		/**
1182
		 * Add bidirectional data binding links between inputs and the setting(s).
1183
		 *
1184
		 * This is copied from wp.customize.Control.prototype.initialize(). It
1185
		 * should be changed in Core to be applied once the control is embedded.
1186
		 *
1187
		 * @private
1188
		 * @returns {null}
1189
		 */
1190
		_setUpSettingRootLinks: function() {
1191
			var control = this,
1192
			    nodes   = control.container.find( '[data-customize-setting-link]' );
1193
1194
			nodes.each( function() {
1195
				var node = jQuery( this );
1196
1197
				wp.customize( node.data( 'customizeSettingLink' ), function( setting ) {
1198
					var element = new wp.customize.Element( node );
1199
					control.elements.push( element );
1200
					element.sync( setting );
1201
					element.set( setting() );
1202
				});
1203
			});
1204
		},
1205
1206
		/**
1207
		 * Add bidirectional data binding links between inputs and the setting properties.
1208
		 *
1209
		 * @private
1210
		 * @returns {null}
1211
		 */
1212
		_setUpSettingPropertyLinks: function() {
1213
			var control = this,
1214
			    nodes;
1215
1216
			if ( ! control.setting ) {
1217
				return;
1218
			}
1219
1220
			nodes = control.container.find( '[data-customize-setting-property-link]' );
1221
1222
			nodes.each( function() {
1223
				var node = jQuery( this ),
1224
				    element,
1225
				    propertyName = node.data( 'customizeSettingPropertyLink' );
1226
1227
				element = new wp.customize.Element( node );
1228
				control.propertyElements.push( element );
1229
				element.set( control.setting()[ propertyName ] );
1230
1231
				element.bind( function( newPropertyValue ) {
1232
					var newSetting = control.setting();
1233
					if ( newPropertyValue === newSetting[ propertyName ] ) {
1234
						return;
1235
					}
1236
					newSetting = _.clone( newSetting );
1237
					newSetting[ propertyName ] = newPropertyValue;
1238
					control.setting.set( newSetting );
1239
				} );
1240
				control.setting.bind( function( newValue ) {
1241
					if ( newValue[ propertyName ] !== element.get() ) {
1242
						element.set( newValue[ propertyName ] );
1243
					}
1244
				} );
1245
			});
1246
		},
1247
1248
		/**
1249
		 * @inheritdoc
1250
		 */
1251
		ready: function() {
1252
			var control = this;
1253
1254
			control._setUpSettingRootLinks();
1255
			control._setUpSettingPropertyLinks();
1256
1257
			wp.customize.Control.prototype.ready.call( control );
1258
1259
			control.deferred.embedded.done( function() {
1260
				control.initKirkiControl( control );
1261
			});
1262
		},
1263
1264
		/**
1265
		 * Embed the control in the document.
1266
		 *
1267
		 * Override the embed() method to do nothing,
1268
		 * so that the control isn't embedded on load,
1269
		 * unless the containing section is already expanded.
1270
		 *
1271
		 * @returns {null}
1272
		 */
1273
		embed: function() {
1274
			var control   = this,
1275
			    sectionId = control.section();
1276
1277
			if ( ! sectionId ) {
1278
				return;
1279
			}
1280
1281
			wp.customize.section( sectionId, function( section ) {
1282
				if ( 'kirki-expanded' === section.params.type || section.expanded() || wp.customize.settings.autofocus.control === control.id ) {
1283
					control.actuallyEmbed();
1284
				} else {
1285
					section.expanded.bind( function( expanded ) {
1286
						if ( expanded ) {
1287
							control.actuallyEmbed();
1288
						}
1289
					} );
1290
				}
1291
			} );
1292
		},
1293
1294
		/**
1295
		 * Deferred embedding of control when actually
1296
		 *
1297
		 * This function is called in Section.onChangeExpanded() so the control
1298
		 * will only get embedded when the Section is first expanded.
1299
		 *
1300
		 * @returns {null}
1301
		 */
1302
		actuallyEmbed: function() {
1303
			var control = this;
1304
			if ( 'resolved' === control.deferred.embedded.state() ) {
1305
				return;
1306
			}
1307
			control.renderContent();
1308
			control.deferred.embedded.resolve(); // This triggers control.ready().
1309
		},
1310
1311
		/**
1312
		 * This is not working with autofocus.
1313
		 *
1314
		 * @param {object} [args] Args.
1315
		 * @returns {null}
1316
		 */
1317
		focus: function( args ) {
1318
			var control = this;
1319
			control.actuallyEmbed();
1320
			wp.customize.Control.prototype.focus.call( control, args );
1321
		},
1322
1323
		/**
1324
		 * Additional actions that run on ready.
1325
		 *
1326
		 * @param {object} [args] Args.
1327
		 * @returns {null}
1328
		 */
1329
		initKirkiControl: function( control ) {
1330
			if ( 'undefined' !== typeof kirki.control[ control.params.type ] ) {
1331
				kirki.control[ control.params.type ].init( control );
1332
				return;
1333
			}
1334
1335
			// Save the value
1336
			this.container.on( 'change keyup paste click', 'input', function() {
1337
				control.setting.set( jQuery( this ).val() );
1338
			});
1339
		},
1340
1341
		kirkiValidateCSSValue: function( value ) {
1342
1343
			var validUnits = ['rem', 'em', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch', 'vh', 'vw', 'vmin', 'vmax'],
1344
				numericValue,
1345
				unit;
1346
1347
			// 0 is always a valid value, and we can't check calc() values effectively.
1348
			if ( '0' === value || ( 0 <= value.indexOf( 'calc(' ) && 0 <= value.indexOf( ')' ) ) ) {
1349
				return true;
1350
			}
1351
1352
			if ( 'auto' === value || 'inherit' === value || 'initial' === value ) {
1353
				return true;
1354
			}
1355
1356
			// Get the numeric value.
1357
			numericValue = parseFloat( value );
1358
1359
			// Get the unit
1360
			unit = value.replace( numericValue, '' );
1361
1362
			// Check the validity of the numeric value and units.
1363
			if ( isNaN( numericValue ) || -1 === jQuery.inArray( unit, validUnits ) ) {
1364
				return false;
1365
			}
1366
			return true;
1367
		}
1368
	});
1369
})();
1370
1371
_.each( kirki.control, function( obj, type ) {
1372
	wp.customize.controlConstructor[ type ] = wp.customize.kirkiDynamicControl.extend({});
1373
} );
1374
/* global kirkiControlLoader */
1375
wp.customize.controlConstructor['kirki-background'] = wp.customize.Control.extend({
1376
1377
	// When we're finished loading continue processing
1378
	ready: function() {
1379
1380
		'use strict';
1381
1382
		var control = this;
1383
1384
		// Init the control.
1385
		if ( ! _.isUndefined( window.kirkiControlLoader ) && _.isFunction( kirkiControlLoader ) ) {
1386
			kirkiControlLoader( control );
1387
		} else {
1388
			control.initKirkiControl();
1389
		}
1390
	},
1391
1392
	initKirkiControl: function() {
1393
1394
		var control = this,
1395
		    value   = control.setting._value,
1396
		    picker  = control.container.find( '.kirki-color-control' );
1397
1398
		// Hide unnecessary controls if the value doesn't have an image.
1399
		if ( _.isUndefined( value['background-image'] ) || '' === value['background-image'] ) {
1400
			control.container.find( '.background-wrapper > .background-repeat' ).hide();
1401
			control.container.find( '.background-wrapper > .background-position' ).hide();
1402
			control.container.find( '.background-wrapper > .background-size' ).hide();
1403
			control.container.find( '.background-wrapper > .background-attachment' ).hide();
1404
		}
1405
1406
		// Color.
1407
		picker.wpColorPicker({
1408
			change: function() {
1409
				setTimeout( function() {
1410
					control.saveValue( 'background-color', picker.val() );
1411
				}, 100 );
1412
			}
1413
		});
1414
1415
		// Background-Repeat.
1416
		control.container.on( 'change', '.background-repeat select', function() {
1417
			control.saveValue( 'background-repeat', jQuery( this ).val() );
1418
		});
1419
1420
		// Background-Size.
1421
		control.container.on( 'change click', '.background-size input', function() {
1422
			control.saveValue( 'background-size', jQuery( this ).val() );
1423
		});
1424
1425
		// Background-Position.
1426
		control.container.on( 'change', '.background-position select', function() {
1427
			control.saveValue( 'background-position', jQuery( this ).val() );
1428
		});
1429
1430
		// Background-Attachment.
1431
		control.container.on( 'change click', '.background-attachment input', function() {
1432
			control.saveValue( 'background-attachment', jQuery( this ).val() );
1433
		});
1434
1435
		// Background-Image.
1436
		control.container.on( 'click', '.background-image-upload-button', function( e ) {
1437
			var image = wp.media({ multiple: false }).open().on( 'select', function() {
1438
1439
				// This will return the selected image from the Media Uploader, the result is an object.
1440
				var uploadedImage = image.state().get( 'selection' ).first(),
1441
				    previewImage   = uploadedImage.toJSON().sizes.full.url,
1442
				    imageUrl,
1443
				    imageID,
1444
				    imageWidth,
1445
				    imageHeight,
1446
				    preview,
1447
				    removeButton;
1448
1449
				if ( ! _.isUndefined( uploadedImage.toJSON().sizes.medium ) ) {
1450
					previewImage = uploadedImage.toJSON().sizes.medium.url;
1451
				} else if ( ! _.isUndefined( uploadedImage.toJSON().sizes.thumbnail ) ) {
1452
					previewImage = uploadedImage.toJSON().sizes.thumbnail.url;
1453
				}
1454
1455
				imageUrl    = uploadedImage.toJSON().sizes.full.url;
1456
				imageID     = uploadedImage.toJSON().id;
1457
				imageWidth  = uploadedImage.toJSON().width;
1458
				imageHeight = uploadedImage.toJSON().height;
1459
1460
				// Show extra controls if the value has an image.
1461
				if ( '' !== imageUrl ) {
1462
					control.container.find( '.background-wrapper > .background-repeat, .background-wrapper > .background-position, .background-wrapper > .background-size, .background-wrapper > .background-attachment' ).show();
1463
				}
1464
1465
				control.saveValue( 'background-image', imageUrl );
1466
				preview      = control.container.find( '.placeholder, .thumbnail' );
1467
				removeButton = control.container.find( '.background-image-upload-remove-button' );
1468
1469
				if ( preview.length ) {
1470
					preview.removeClass().addClass( 'thumbnail thumbnail-image' ).html( '<img src="' + previewImage + '" alt="" />' );
1471
				}
1472
				if ( removeButton.length ) {
1473
					removeButton.show();
1474
				}
1475
		    });
1476
1477
			e.preventDefault();
1478
		});
1479
1480
		control.container.on( 'click', '.background-image-upload-remove-button', function( e ) {
1481
1482
			var preview,
1483
			    removeButton;
1484
1485
			e.preventDefault();
1486
1487
			control.saveValue( 'background-image', '' );
1488
1489
			preview      = control.container.find( '.placeholder, .thumbnail' );
1490
			removeButton = control.container.find( '.background-image-upload-remove-button' );
1491
1492
			// Hide unnecessary controls.
1493
			control.container.find( '.background-wrapper > .background-repeat' ).hide();
1494
			control.container.find( '.background-wrapper > .background-position' ).hide();
1495
			control.container.find( '.background-wrapper > .background-size' ).hide();
1496
			control.container.find( '.background-wrapper > .background-attachment' ).hide();
1497
1498
			if ( preview.length ) {
1499
				preview.removeClass().addClass( 'placeholder' ).html( 'No file selected' );
1500
			}
1501
			if ( removeButton.length ) {
1502
				removeButton.hide();
1503
			}
1504
		});
1505
	},
1506
1507
	/**
1508
	 * Saves the value.
1509
	 */
1510
	saveValue: function( property, value ) {
1511
1512
		var control = this,
1513
		    input   = jQuery( '#customize-control-' + control.id.replace( '[', '-' ).replace( ']', '' ) + ' .background-hidden-value' ),
1514
		    val     = control.setting._value;
1515
1516
		val[ property ] = value;
1517
1518
		jQuery( input ).attr( 'value', JSON.stringify( val ) ).trigger( 'change' );
1519
		control.setting.set( val );
1520
	}
1521
});
1522
wp.customize.controlConstructor['kirki-color-palette'] = wp.customize.kirkiDynamicControl.extend({});
1523
wp.customize.controlConstructor['kirki-dashicons'] = wp.customize.kirkiDynamicControl.extend({});
1524
wp.customize.controlConstructor['kirki-date'] = wp.customize.kirkiDynamicControl.extend({
1525
1526
	initKirkiControl: function() {
1527
1528
		var control  = this;
1529
1530
		// Only add in WP 4.9+.
1531
		if ( _.isUndefined( wp.customize.DateTimeControl ) ) {
1532
			return;
1533
		}
1534
1535
		// New method for the DateTime control.
1536
		wp.customize.control.add( new wp.customize.DateTimeControl( control.id, {
1537
			section: control.params.section,
1538
			priority: control.params.priority,
1539
			label: control.params.label,
1540
			description: control.params.description,
1541
			settings: { 'default': control.id },
1542
			'default': control.params['default']
1543
		} ) );
1544
	}
1545
});
1546
/* global dimensionkirkiL10n */
1547
wp.customize.controlConstructor['kirki-dimension'] = wp.customize.kirkiDynamicControl.extend({
1548
1549
	initKirkiControl: function() {
1550
1551
		var control = this,
1552
		    value;
1553
1554
		// Notifications.
1555
		control.kirkiNotifications();
1556
1557
		// Save the value
1558
		this.container.on( 'change keyup paste', 'input', function() {
1559
1560
			value = jQuery( this ).val();
1561
			control.setting.set( value );
1562
		});
1563
	},
1564
1565
	/**
1566
	 * Handles notifications.
1567
	 */
1568
	kirkiNotifications: function() {
1569
1570
		var control = this;
1571
1572
		wp.customize( control.id, function( setting ) {
1573
			setting.bind( function( value ) {
1574
				var code = 'long_title';
1575
1576
				if ( false === control.kirkiValidateCSSValue( value ) ) {
1577
					setting.notifications.add( code, new wp.customize.Notification(
1578
						code,
1579
						{
1580
							type: 'warning',
1581
							message: dimensionkirkiL10n['invalid-value']
1582
						}
1583
					) );
1584
				} else {
1585
					setting.notifications.remove( code );
1586
				}
1587
			} );
1588
		} );
1589
	}
1590
});
1591
/* global dimensionskirkiL10n */
1592
wp.customize.controlConstructor['kirki-dimensions'] = wp.customize.kirkiDynamicControl.extend({
1593
1594
	initKirkiControl: function() {
1595
1596
		var control     = this,
1597
		    subControls = control.params.choices.controls,
1598
		    value       = {},
1599
		    subsArray   = [],
1600
		    i;
1601
1602
		_.each( subControls, function( v, i ) {
1603
			if ( true === v ) {
1604
				subsArray.push( i );
1605
			}
1606
		} );
1607
1608
		for ( i = 0; i < subsArray.length; i++ ) {
1609
			value[ subsArray[ i ] ] = control.setting._value[ subsArray[ i ] ];
1610
			control.updateDimensionsValue( subsArray[ i ], value );
1611
		}
1612
	},
1613
1614
	/**
1615
	 * Updates the value.
1616
	 */
1617
	updateDimensionsValue: function( context, value ) {
1618
1619
		var control = this;
1620
1621
		control.container.on( 'change keyup paste', '.' + context + ' input', function() {
1622
			value[ context ] = jQuery( this ).val();
1623
1624
			// Notifications.
1625
			control.kirkiNotifications();
1626
1627
			// Save the value
1628
			control.saveValue( value );
1629
		});
1630
	},
1631
1632
	/**
1633
	 * Saves the value.
1634
	 */
1635
	saveValue: function( value ) {
1636
1637
		var control  = this,
1638
		    newValue = {};
1639
1640
		_.each( value, function( newSubValue, i ) {
1641
			newValue[ i ] = newSubValue;
1642
		});
1643
1644
		control.setting.set( newValue );
1645
	},
1646
1647
	/**
1648
	 * Handles notifications.
1649
	 */
1650
	kirkiNotifications: function() {
1651
1652
		var control = this;
1653
1654
		wp.customize( control.id, function( setting ) {
1655
			setting.bind( function( value ) {
1656
				var code = 'long_title',
1657
				    subs = {},
1658
				    message;
1659
1660
				setting.notifications.remove( code );
1661
1662
				_.each( value, function( val, direction ) {
1663
					if ( false === control.kirkiValidateCSSValue( val ) ) {
1664
						subs[ direction ] = val;
1665
					} else {
1666
						delete subs[ direction ];
1667
					}
1668
				} );
1669
1670
				if ( ! _.isEmpty( subs ) ) {
1671
					message = dimensionskirkiL10n['invalid-value'] + ' (' + _.values( subs ).toString() + ') ';
1672
					setting.notifications.add( code, new wp.customize.Notification( code, {
1673
						type: 'warning',
1674
						message: message
1675
					} ) );
1676
					return;
1677
				}
1678
				setting.notifications.remove( code );
1679
			} );
1680
		} );
1681
	}
1682
});
1683
/* global tinyMCE */
1684
wp.customize.controlConstructor['kirki-editor'] = wp.customize.kirkiDynamicControl.extend({
1685
1686
	initKirkiControl: function() {
1687
1688
		var control = this,
1689
		    element = control.container.find( 'textarea' ),
1690
		    id      = 'kirki-editor-' + control.id.replace( '[', '' ).replace( ']', '' ),
1691
		    editor;
1692
1693
		wp.editor.initialize( id, {
1694
			tinymce: {
1695
				wpautop: true
1696
			},
1697
			quicktags: true,
1698
			mediaButtons: true
1699
		});
1700
1701
		editor = tinyMCE.get( id );
1702
1703
		if ( editor ) {
1704
			editor.onChange.add( function( ed ) {
1705
				var content;
1706
1707
				ed.save();
1708
				content = editor.getContent();
1709
				element.val( content ).trigger( 'change' );
1710
				wp.customize.instance( control.id ).set( content );
1711
			});
1712
		}
1713
	}
1714
});
1715
/* global fontAwesomeJSON */
1716
wp.customize.controlConstructor['kirki-fontawesome'] = wp.customize.kirkiDynamicControl.extend({
1717
1718
	initKirkiControl: function() {
1719
1720
		var control  = this,
1721
		    element  = this.container.find( 'select' ),
1722
		    icons    = jQuery.parseJSON( fontAwesomeJSON ),
1723
		    selectValue,
1724
		    selectWooOptions = {
1725
				data: [],
1726
				escapeMarkup: function( markup ) {
1727
					return markup;
1728
				},
1729
				templateResult: function( val ) {
1730
					return '<i class="fa fa-lg fa-' + val.id + '" aria-hidden="true"></i>' + ' ' + val.text;
1731
				},
1732
				templateSelection: function( val ) {
1733
					return '<i class="fa fa-lg fa-' + val.id + '" aria-hidden="true"></i>' + ' ' + val.text;
1734
				}
1735
		    },
1736
		    select;
1737
1738
		_.each( icons.icons, function( icon ) {
1739
			selectWooOptions.data.push({
1740
				id: icon.id,
1741
				text: icon.name
1742
			});
1743
		});
1744
1745
		select = jQuery( element ).selectWoo( selectWooOptions );
1746
1747
		select.on( 'change', function() {
1748
			selectValue = jQuery( this ).val();
1749
			control.setting.set( selectValue );
1750
		});
1751
		select.val( control.setting._value ).trigger( 'change' );
1752
	}
1753
});
1754
/* global kirkiControlLoader */
1755
wp.customize.controlConstructor['kirki-image'] = wp.customize.Control.extend({
1756
1757
	// When we're finished loading continue processing
1758
	ready: function() {
1759
1760
		'use strict';
1761
1762
		var control = this;
1763
1764
		// Init the control.
1765
		if ( ! _.isUndefined( window.kirkiControlLoader ) && _.isFunction( kirkiControlLoader ) ) {
1766
			kirkiControlLoader( control );
1767
		} else {
1768
			control.initKirkiControl();
1769
		}
1770
	},
1771
1772
	initKirkiControl: function() {
1773
1774
		var control       = this,
1775
		    value         = control.getValue(),
1776
		    saveAs        = ( ! _.isUndefined( control.params.choices ) && ! _.isUndefined( control.params.choices.save_as ) ) ? control.params.choices.save_as : 'url',
1777
		    preview       = control.container.find( '.placeholder, .thumbnail' ),
1778
		    previewImage  = ( 'array' === saveAs ) ? value.url : value,
1779
		    removeButton  = control.container.find( '.image-upload-remove-button' ),
1780
		    defaultButton = control.container.find( '.image-default-button' );
1781
1782
		control.container.find( '.kirki-controls-loading-spinner' ).hide();
1783
1784
		// Tweaks for save_as = id.
1785
		if ( ( 'id' === saveAs || 'ID' === saveAs ) && '' !== value ) {
1786
			wp.media.attachment( value ).fetch().then( function() {
1787
				setTimeout( function() {
1788
					var url = wp.media.attachment( value ).get( 'url' );
1789
					preview.removeClass().addClass( 'thumbnail thumbnail-image' ).html( '<img src="' + url + '" alt="" />' );
1790
				}, 700 );
1791
			} );
1792
		}
1793
1794
		// If value is not empty, hide the "default" button.
1795
		if ( ( 'url' === saveAs && '' !== value ) || ( 'array' === saveAs && ! _.isUndefined( value.url ) && '' !== value.url ) ) {
1796
			control.container.find( 'image-default-button' ).hide();
1797
		}
1798
1799
		// If value is empty, hide the "remove" button.
1800
		if ( ( 'url' === saveAs && '' === value ) || ( 'array' === saveAs && ( _.isUndefined( value.url ) || '' === value.url ) ) ) {
1801
			removeButton.hide();
1802
		}
1803
1804
		// If value is default, hide the default button.
1805
		if ( value === control.params['default'] ) {
1806
			control.container.find( 'image-default-button' ).hide();
1807
		}
1808
1809
		if ( '' !== previewImage ) {
1810
			preview.removeClass().addClass( 'thumbnail thumbnail-image' ).html( '<img src="' + previewImage + '" alt="" />' );
1811
		}
1812
1813
		control.container.on( 'click', '.image-upload-button', function( e ) {
1814
			var image = wp.media({ multiple: false }).open().on( 'select', function() {
1815
1816
					// This will return the selected image from the Media Uploader, the result is an object.
1817
					var uploadedImage = image.state().get( 'selection' ).first(),
1818
					    previewImage  = uploadedImage.toJSON().sizes.full.url;
1819
1820
					if ( ! _.isUndefined( uploadedImage.toJSON().sizes.medium ) ) {
1821
						previewImage = uploadedImage.toJSON().sizes.medium.url;
1822
					} else if ( ! _.isUndefined( uploadedImage.toJSON().sizes.thumbnail ) ) {
1823
						previewImage = uploadedImage.toJSON().sizes.thumbnail.url;
1824
					}
1825
1826
					if ( 'array' === saveAs ) {
1827
						control.saveValue( 'id', uploadedImage.toJSON().id );
1828
						control.saveValue( 'url', uploadedImage.toJSON().sizes.full.url );
1829
						control.saveValue( 'width', uploadedImage.toJSON().width );
1830
						control.saveValue( 'height', uploadedImage.toJSON().height );
1831
					} else if ( 'id' === saveAs ) {
1832
						control.saveValue( 'id', uploadedImage.toJSON().id );
1833
					} else {
1834
						control.saveValue( 'url', uploadedImage.toJSON().sizes.full.url );
1835
					}
1836
1837
					if ( preview.length ) {
1838
						preview.removeClass().addClass( 'thumbnail thumbnail-image' ).html( '<img src="' + previewImage + '" alt="" />' );
1839
					}
1840
					if ( removeButton.length ) {
1841
						removeButton.show();
1842
						defaultButton.hide();
1843
					}
1844
			    });
1845
1846
			e.preventDefault();
1847
		});
1848
1849
		control.container.on( 'click', '.image-upload-remove-button', function( e ) {
1850
1851
			var preview,
1852
			    removeButton,
1853
			    defaultButton;
1854
1855
			e.preventDefault();
1856
1857
			control.saveValue( 'id', '' );
1858
			control.saveValue( 'url', '' );
1859
			control.saveValue( 'width', '' );
1860
			control.saveValue( 'height', '' );
1861
1862
			preview       = control.container.find( '.placeholder, .thumbnail' );
1863
			removeButton  = control.container.find( '.image-upload-remove-button' );
1864
			defaultButton = control.container.find( '.image-default-button' );
1865
1866
			if ( preview.length ) {
1867
				preview.removeClass().addClass( 'placeholder' ).html( 'No file selected' );
1868
			}
1869
			if ( removeButton.length ) {
1870
				removeButton.hide();
1871
				if ( jQuery( defaultButton ).hasClass( 'button' ) ) {
1872
					defaultButton.show();
1873
				}
1874
			}
1875
		});
1876
1877
		control.container.on( 'click', '.image-default-button', function( e ) {
1878
1879
			var preview,
1880
			    removeButton,
1881
			    defaultButton;
1882
1883
			e.preventDefault();
1884
1885
			control.saveValue( 'url', control.params['default'] );
1886
1887
			preview       = control.container.find( '.placeholder, .thumbnail' );
1888
			removeButton  = control.container.find( '.image-upload-remove-button' );
1889
			defaultButton = control.container.find( '.image-default-button' );
1890
1891
			if ( preview.length ) {
1892
				preview.removeClass().addClass( 'thumbnail thumbnail-image' ).html( '<img src="' + control.params['default'] + '" alt="" />' );
1893
			}
1894
			if ( removeButton.length ) {
1895
				removeButton.show();
1896
				defaultButton.hide();
1897
			}
1898
		});
1899
	},
1900
1901
	/**
1902
	 * Gets the value.
1903
	 */
1904
	getValue: function() {
1905
		var control = this,
1906
		    value   = control.setting._value,
1907
		    saveAs  = ( ! _.isUndefined( control.params.choices ) && ! _.isUndefined( control.params.choices.save_as ) ) ? control.params.choices.save_as : 'url';
1908
1909
		if ( 'array' === saveAs && _.isString( value ) ) {
1910
			value = {
1911
				url: value
1912
			};
1913
		}
1914
		return value;
1915
	},
1916
1917
	/**
1918
	 * Saves the value.
1919
	 */
1920
	saveValue: function( property, value ) {
1921
		var control   = this,
1922
		    valueOld  = control.setting._value,
1923
		    saveAs    = ( ! _.isUndefined( control.params.choices ) && ! _.isUndefined( control.params.choices.save_as ) ) ? control.params.choices.save_as : 'url';
1924
1925
		if ( 'array' === saveAs ) {
1926
			if ( _.isString( valueOld ) ) {
1927
				valueOld = {};
1928
			}
1929
			valueOld[ property ] = value;
1930
			control.setting.set( valueOld );
1931
			control.container.find( 'button' ).trigger( 'change' );
1932
			return;
1933
		}
1934
		control.setting.set( value );
1935
		control.container.find( 'button' ).trigger( 'change' );
1936
	}
1937
});
1938
wp.customize.controlConstructor['kirki-multicheck'] = wp.customize.kirkiDynamicControl.extend({
1939
1940
	initKirkiControl: function() {
1941
1942
		var control = this;
1943
1944
		// Save the value
1945
		control.container.on( 'change', 'input', function() {
1946
			var value = [],
1947
			    i = 0;
1948
1949
			// Build the value as an object using the sub-values from individual checkboxes.
1950
			jQuery.each( control.params.choices, function( key ) {
1951
				if ( control.container.find( 'input[value="' + key + '"]' ).is( ':checked' ) ) {
1952
					value[ i ] = key;
1953
					i++;
1954
				}
1955
			});
1956
1957
			// Update the value in the customizer.
1958
			control.setting.set( value );
1959
		});
1960
	}
1961
});
1962
/* global kirkiControlLoader */
1963
wp.customize.controlConstructor['kirki-multicolor'] = wp.customize.Control.extend({
1964
1965
	// When we're finished loading continue processing
1966
	ready: function() {
1967
1968
		'use strict';
1969
1970
		var control = this;
1971
1972
		// Init the control.
1973
		if ( ! _.isUndefined( window.kirkiControlLoader ) && _.isFunction( kirkiControlLoader ) ) {
1974
			kirkiControlLoader( control );
1975
		} else {
1976
			control.initKirkiControl();
1977
		}
1978
	},
1979
1980
	initKirkiControl: function() {
1981
1982
		'use strict';
1983
1984
		var control = this,
1985
		    colors  = control.params.choices,
1986
		    keys    = Object.keys( colors ),
1987
		    value   = this.params.value,
1988
		    i       = 0;
1989
1990
		// Proxy function that handles changing the individual colors
1991
		function kirkiMulticolorChangeHandler( control, value, subSetting ) {
1992
1993
			var picker = control.container.find( '.multicolor-index-' + subSetting ),
1994
			    args   = {
1995
					change: function() {
1996
1997
						// Color controls require a small delay.
1998
						setTimeout( function() {
1999
2000
							// Set the value.
2001
							control.saveValue( subSetting, picker.val() );
2002
2003
							// Trigger the change.
2004
							control.container.find( '.multicolor-index-' + subSetting ).trigger( 'change' );
2005
						}, 100 );
2006
					}
2007
			    };
2008
2009
			if ( _.isObject( colors.irisArgs ) ) {
2010
				_.each( colors.irisArgs, function( irisValue, irisKey ) {
2011
					args[ irisKey ] = irisValue;
2012
				});
2013
			}
2014
2015
			// Did we change the value?
2016
			picker.wpColorPicker( args );
2017
		}
2018
2019
		// Colors loop
2020
		while ( i < Object.keys( colors ).length ) {
2021
			kirkiMulticolorChangeHandler( this, value, keys[ i ] );
2022
			i++;
2023
		}
2024
	},
2025
2026
	/**
2027
	 * Saves the value.
2028
	 */
2029
	saveValue: function( property, value ) {
2030
2031
		var control = this,
2032
		    input   = control.container.find( '.multicolor-hidden-value' ),
2033
		    val     = control.setting._value;
2034
2035
		val[ property ] = value;
2036
2037
		jQuery( input ).attr( 'value', JSON.stringify( val ) ).trigger( 'change' );
2038
		control.setting.set( val );
2039
	}
2040
});
2041
wp.customize.controlConstructor['kirki-number'] = wp.customize.kirkiDynamicControl.extend({
2042
2043
	initKirkiControl: function() {
2044
2045
		var control = this,
2046
		    value   = control.setting._value,
2047
		    html    = '',
2048
		    input,
2049
		    up,
2050
		    down;
2051
2052
		// Make sure we use default values if none are define for some arguments.
2053
		control.params.choices = _.defaults( control.params.choices, {
2054
			min: 0,
2055
			max: 100,
2056
			step: 1
2057
		} );
2058
2059
		// Make sure we have a valid value.
2060
		if ( isNaN( value ) || '' === value ) {
2061
			value = ( 0 > control.params.choices.min && 0 < control.params.choices.max ) ? 0 : control.params.choices.min;
2062
		}
2063
		value = parseFloat( value );
2064
2065
		// If step is 'any', set to 0.001.
2066
		control.params.choices.step = ( 'any' === control.params.choices.step ) ? 0.001 : control.params.choices.step;
2067
2068
		// Make sure choices are properly formtted as numbers.
2069
		control.params.choices.min  = parseFloat( control.params.choices.min );
2070
		control.params.choices.max  = parseFloat( control.params.choices.max );
2071
		control.params.choices.step = parseFloat( control.params.choices.step );
2072
2073
		// Build the HTML for the control.
2074
		html += '<label>';
2075
		if ( control.params.label ) {
2076
			html += '<span class="customize-control-title">' + control.params.label + '</span>';
2077
		}
2078
		if ( control.params.description ) {
2079
			html += '<span class="description customize-control-description">' + control.params.description + '</span>';
2080
		}
2081
		html += '<div class="customize-control-content">';
2082
		html += '<input ' + control.params.inputAttrs + ' type="text" ' + control.params.link + ' value="' + value + '" />';
2083
		html += '<div class="quantity button minus">-</div>';
2084
		html += '<div class="quantity button plus">+</div>';
2085
		html += '</div>';
2086
		html += '</label>';
2087
2088
		control.container.html( html );
2089
2090
		input = control.container.find( 'input' );
2091
		up    = control.container.find( '.plus' );
2092
		down  = control.container.find( '.minus' );
2093
2094
		up.click( function() {
2095
			var oldVal = parseFloat( input.val() ),
2096
			    newVal;
2097
2098
			newVal = ( oldVal >= control.params.choices.max ) ? oldVal : oldVal + control.params.choices.step;
2099
2100
			input.val( newVal );
2101
			input.trigger( 'change' );
2102
		} );
2103
2104
		down.click( function() {
2105
			var oldVal = parseFloat( input.val() ),
2106
			    newVal;
2107
2108
			newVal = ( oldVal <= control.params.choices.min ) ? oldVal : oldVal - control.params.choices.step;
2109
2110
			input.val( newVal );
2111
			input.trigger( 'change' );
2112
		} );
2113
2114
		this.container.on( 'change keyup paste click', 'input', function() {
2115
			control.setting.set( jQuery( this ).val() );
2116
		});
2117
	}
2118
});
2119
wp.customize.controlConstructor['kirki-palette'] = wp.customize.kirkiDynamicControl.extend({});
2120
/* global kirkiSetSettingValue */
2121
wp.customize.controlConstructor['kirki-preset'] = wp.customize.kirkiDynamicControl.extend({
2122
2123
	initKirkiControl: function() {
2124
2125
		var control = this,
2126
		    selectValue;
2127
2128
		// Trigger a change
2129
		this.container.on( 'change', 'select', function() {
2130
2131
			// Get the control's value
2132
			selectValue = jQuery( this ).val();
2133
2134
			// Update the value using the customizer API and trigger the "save" button
2135
			control.setting.set( selectValue );
2136
2137
			// We have to get the choices of this control
2138
			// and then start parsing them to see what we have to do for each of the choices.
2139
			jQuery.each( control.params.choices, function( key, value ) {
2140
2141
				// If the current value of the control is the key of the choice,
2142
				// then we can continue processing, Otherwise there's no reason to do anything.
2143
				if ( selectValue === key ) {
2144
2145
					// Each choice has an array of settings defined in it.
2146
					// We'll have to loop through them all and apply the changes needed to them.
2147
					jQuery.each( value.settings, function( presetSetting, presetSettingValue ) {
2148
						kirkiSetSettingValue.set( presetSetting, presetSettingValue );
2149
					});
2150
				}
2151
			});
2152
			wp.customize.previewer.refresh();
2153
		});
2154
	}
2155
});
2156
wp.customize.controlConstructor['kirki-radio-buttonset'] = wp.customize.kirkiDynamicControl.extend({});
2157
wp.customize.controlConstructor['kirki-radio-image'] = wp.customize.kirkiDynamicControl.extend({});
2158
/* global kirkiControlLoader */
2159
var RepeaterRow = function( rowIndex, container, label, control ) {
2160
2161
	'use strict';
2162
2163
	var self        = this;
2164
	this.rowIndex   = rowIndex;
2165
	this.container  = container;
2166
	this.label      = label;
2167
	this.header     = this.container.find( '.repeater-row-header' ),
2168
2169
	this.header.on( 'click', function() {
2170
		self.toggleMinimize();
2171
	});
2172
2173
	this.container.on( 'click', '.repeater-row-remove', function() {
2174
		self.remove();
2175
	});
2176
2177
	this.header.on( 'mousedown', function() {
2178
		self.container.trigger( 'row:start-dragging' );
2179
	});
2180
2181
	this.container.on( 'keyup change', 'input, select, textarea', function( e ) {
2182
		self.container.trigger( 'row:update', [ self.rowIndex, jQuery( e.target ).data( 'field' ), e.target ] );
2183
	});
2184
2185
	this.setRowIndex = function( rowIndex ) {
2186
		this.rowIndex = rowIndex;
2187
		this.container.attr( 'data-row', rowIndex );
2188
		this.container.data( 'row', rowIndex );
2189
		this.updateLabel();
2190
	};
2191
2192
	this.toggleMinimize = function() {
2193
2194
		// Store the previous state.
2195
		this.container.toggleClass( 'minimized' );
2196
		this.header.find( '.dashicons' ).toggleClass( 'dashicons-arrow-up' ).toggleClass( 'dashicons-arrow-down' );
2197
	};
2198
2199
	this.remove = function() {
2200
		this.container.slideUp( 300, function() {
2201
			jQuery( this ).detach();
2202
		});
2203
		this.container.trigger( 'row:remove', [ this.rowIndex ] );
2204
	};
2205
2206
	this.updateLabel = function() {
2207
		var rowLabelField,
2208
		    rowLabel,
2209
		    rowLabelSelector;
2210
2211
		if ( 'field' === this.label.type ) {
2212
			rowLabelField = this.container.find( '.repeater-field [data-field="' + this.label.field + '"]' );
2213
			if ( _.isFunction( rowLabelField.val ) ) {
2214
				rowLabel = rowLabelField.val();
2215
				if ( '' !== rowLabel ) {
2216
					if ( ! _.isUndefined( control.params.fields[ this.label.field ] ) ) {
2217
						if ( ! _.isUndefined( control.params.fields[ this.label.field ].type ) ) {
2218
							if ( 'select' === control.params.fields[ this.label.field ].type ) {
2219
								if ( ! _.isUndefined( control.params.fields[ this.label.field ].choices ) && ! _.isUndefined( control.params.fields[ this.label.field ].choices[ rowLabelField.val() ] ) ) {
2220
									rowLabel = control.params.fields[ this.label.field ].choices[ rowLabelField.val() ];
2221
								}
2222
							} else if ( 'radio' === control.params.fields[ this.label.field ].type || 'radio-image' === control.params.fields[ this.label.field ].type ) {
2223
								rowLabelSelector = control.selector + ' [data-row="' + this.rowIndex + '"] .repeater-field [data-field="' + this.label.field + '"]:checked';
2224
								rowLabel = jQuery( rowLabelSelector ).val();
2225
							}
2226
						}
2227
					}
2228
					this.header.find( '.repeater-row-label' ).text( rowLabel );
2229
					return;
2230
				}
2231
			}
2232
		}
2233
		this.header.find( '.repeater-row-label' ).text( this.label.value + ' ' + ( this.rowIndex + 1 ) );
2234
	};
2235
	this.updateLabel();
2236
};
2237
2238
wp.customize.controlConstructor.repeater = wp.customize.Control.extend({
2239
2240
	// When we're finished loading continue processing
2241
	ready: function() {
2242
2243
		'use strict';
2244
2245
		var control = this;
2246
2247
		// Init the control.
2248
		if ( ! _.isUndefined( window.kirkiControlLoader ) && _.isFunction( kirkiControlLoader ) ) {
2249
			kirkiControlLoader( control );
2250
		} else {
2251
			control.initKirkiControl();
2252
		}
2253
	},
2254
2255
	initKirkiControl: function() {
2256
2257
		'use strict';
2258
2259
		var control = this,
2260
		    limit,
2261
		    theNewRow;
2262
2263
		// The current value set in Control Class (set in Kirki_Customize_Repeater_Control::to_json() function)
2264
		var settingValue = this.params.value;
2265
2266
		control.container.find( '.kirki-controls-loading-spinner' ).hide();
2267
2268
		// The hidden field that keeps the data saved (though we never update it)
2269
		this.settingField = this.container.find( '[data-customize-setting-link]' ).first();
2270
2271
		// Set the field value for the first time, we'll fill it up later
2272
		this.setValue( [], false );
2273
2274
		// The DIV that holds all the rows
2275
		this.repeaterFieldsContainer = this.container.find( '.repeater-fields' ).first();
2276
2277
		// Set number of rows to 0
2278
		this.currentIndex = 0;
2279
2280
		// Save the rows objects
2281
		this.rows = [];
2282
2283
		// Default limit choice
2284
		limit = false;
2285
		if ( ! _.isUndefined( this.params.choices.limit ) ) {
2286
			limit = ( 0 >= this.params.choices.limit ) ? false : parseInt( this.params.choices.limit, 10 );
2287
		}
2288
2289
		this.container.on( 'click', 'button.repeater-add', function( e ) {
2290
			e.preventDefault();
2291
			if ( ! limit || control.currentIndex < limit ) {
2292
				theNewRow = control.addRow();
2293
				theNewRow.toggleMinimize();
2294
				control.initColorPicker();
2295
				control.initSelect( theNewRow );
2296
			} else {
2297
				jQuery( control.selector + ' .limit' ).addClass( 'highlight' );
2298
			}
2299
		});
2300
2301
		this.container.on( 'click', '.repeater-row-remove', function() {
2302
			control.currentIndex--;
2303
			if ( ! limit || control.currentIndex < limit ) {
2304
				jQuery( control.selector + ' .limit' ).removeClass( 'highlight' );
2305
			}
2306
		});
2307
2308
		this.container.on( 'click keypress', '.repeater-field-image .upload-button,.repeater-field-cropped_image .upload-button,.repeater-field-upload .upload-button', function( e ) {
2309
			e.preventDefault();
2310
			control.$thisButton = jQuery( this );
2311
			control.openFrame( e );
2312
		});
2313
2314
		this.container.on( 'click keypress', '.repeater-field-image .remove-button,.repeater-field-cropped_image .remove-button', function( e ) {
2315
			e.preventDefault();
2316
			control.$thisButton = jQuery( this );
2317
			control.removeImage( e );
2318
		});
2319
2320
		this.container.on( 'click keypress', '.repeater-field-upload .remove-button', function( e ) {
2321
			e.preventDefault();
2322
			control.$thisButton = jQuery( this );
2323
			control.removeFile( e );
2324
		});
2325
2326
		/**
2327
		 * Function that loads the Mustache template
2328
		 */
2329
		this.repeaterTemplate = _.memoize( function() {
2330
			var compiled,
2331
			    /*
2332
			     * Underscore's default ERB-style templates are incompatible with PHP
2333
			     * when asp_tags is enabled, so WordPress uses Mustache-inspired templating syntax.
2334
			     *
2335
			     * @see trac ticket #22344.
2336
			     */
2337
			    options = {
2338
					evaluate: /<#([\s\S]+?)#>/g,
2339
					interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
2340
					escape: /\{\{([^\}]+?)\}\}(?!\})/g,
2341
					variable: 'data'
2342
			    };
2343
2344
			return function( data ) {
2345
				compiled = _.template( control.container.find( '.customize-control-repeater-content' ).first().html(), null, options );
2346
				return compiled( data );
2347
			};
2348
		});
2349
2350
		// When we load the control, the fields have not been filled up
2351
		// This is the first time that we create all the rows
2352
		if ( settingValue.length ) {
2353
			_.each( settingValue, function( subValue ) {
2354
				theNewRow = control.addRow( subValue );
2355
				control.initColorPicker();
2356
				control.initSelect( theNewRow, subValue );
2357
			});
2358
		}
2359
2360
		// Once we have displayed the rows, we cleanup the values
2361
		this.setValue( settingValue, true, true );
2362
2363
		this.repeaterFieldsContainer.sortable({
2364
			handle: '.repeater-row-header',
2365
			update: function() {
2366
				control.sort();
2367
			}
2368
		});
2369
2370
	},
2371
2372
	/**
2373
	 * Open the media modal.
2374
	 */
2375
	openFrame: function( event ) {
2376
2377
		'use strict';
2378
2379
		if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
2380
			return;
2381
		}
2382
2383
		if ( this.$thisButton.closest( '.repeater-field' ).hasClass( 'repeater-field-cropped_image' ) ) {
2384
			this.initCropperFrame();
2385
		} else {
2386
			this.initFrame();
2387
		}
2388
2389
		this.frame.open();
2390
	},
2391
2392
	initFrame: function() {
2393
2394
		'use strict';
2395
2396
		var libMediaType = this.getMimeType();
2397
2398
		this.frame = wp.media({
2399
			states: [
2400
			new wp.media.controller.Library({
2401
					library:  wp.media.query({ type: libMediaType }),
2402
					multiple: false,
2403
					date:     false
2404
				})
2405
			]
2406
		});
2407
2408
		// When a file is selected, run a callback.
2409
		this.frame.on( 'select', this.onSelect, this );
2410
	},
2411
	/**
2412
	 * Create a media modal select frame, and store it so the instance can be reused when needed.
2413
	 * This is mostly a copy/paste of Core api.CroppedImageControl in /wp-admin/js/customize-control.js
2414
	 */
2415
	initCropperFrame: function() {
2416
2417
		'use strict';
2418
2419
		// We get the field id from which this was called
2420
		var currentFieldId = this.$thisButton.siblings( 'input.hidden-field' ).attr( 'data-field' ),
2421
		    attrs          = [ 'width', 'height', 'flex_width', 'flex_height' ], // A list of attributes to look for
2422
		    libMediaType   = this.getMimeType();
2423
2424
		// Make sure we got it
2425
		if ( _.isString( currentFieldId ) && '' !== currentFieldId ) {
2426
2427
			// Make fields is defined and only do the hack for cropped_image
2428
			if ( _.isObject( this.params.fields[ currentFieldId ] ) && 'cropped_image' === this.params.fields[ currentFieldId ].type ) {
2429
2430
				//Iterate over the list of attributes
2431
				attrs.forEach( function( el ) {
2432
2433
					// If the attribute exists in the field
2434
					if ( ! _.isUndefined( this.params.fields[ currentFieldId ][ el ] ) ) {
2435
2436
						// Set the attribute in the main object
2437
						this.params[ el ] = this.params.fields[ currentFieldId ][ el ];
2438
					}
2439
				}.bind( this ) );
2440
			}
2441
		}
2442
2443
		this.frame = wp.media({
2444
			button: {
2445
				text: 'Select and Crop',
2446
				close: false
2447
			},
2448
			states: [
2449
				new wp.media.controller.Library({
2450
					library:         wp.media.query({ type: libMediaType }),
2451
					multiple:        false,
2452
					date:            false,
2453
					suggestedWidth:  this.params.width,
2454
					suggestedHeight: this.params.height
2455
				}),
2456
				new wp.media.controller.CustomizeImageCropper({
2457
					imgSelectOptions: this.calculateImageSelectOptions,
2458
					control: this
2459
				})
2460
			]
2461
		});
2462
2463
		this.frame.on( 'select', this.onSelectForCrop, this );
2464
		this.frame.on( 'cropped', this.onCropped, this );
2465
		this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
2466
2467
	},
2468
2469
	onSelect: function() {
2470
2471
		'use strict';
2472
2473
		var attachment = this.frame.state().get( 'selection' ).first().toJSON();
2474
2475
		if ( this.$thisButton.closest( '.repeater-field' ).hasClass( 'repeater-field-upload' ) ) {
2476
			this.setFileInRepeaterField( attachment );
2477
		} else {
2478
			this.setImageInRepeaterField( attachment );
2479
		}
2480
	},
2481
2482
	/**
2483
	 * After an image is selected in the media modal, switch to the cropper
2484
	 * state if the image isn't the right size.
2485
	 */
2486
2487
	onSelectForCrop: function() {
2488
2489
		'use strict';
2490
2491
		var attachment = this.frame.state().get( 'selection' ).first().toJSON();
2492
2493
		if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
2494
			this.setImageInRepeaterField( attachment );
2495
		} else {
2496
			this.frame.setState( 'cropper' );
2497
		}
2498
	},
2499
2500
	/**
2501
	 * After the image has been cropped, apply the cropped image data to the setting.
2502
	 *
2503
	 * @param {object} croppedImage Cropped attachment data.
2504
	 */
2505
	onCropped: function( croppedImage ) {
2506
2507
		'use strict';
2508
2509
		this.setImageInRepeaterField( croppedImage );
2510
2511
	},
2512
2513
	/**
2514
	 * Returns a set of options, computed from the attached image data and
2515
	 * control-specific data, to be fed to the imgAreaSelect plugin in
2516
	 * wp.media.view.Cropper.
2517
	 *
2518
	 * @param {wp.media.model.Attachment} attachment
2519
	 * @param {wp.media.controller.Cropper} controller
2520
	 * @returns {Object} Options
2521
	 */
2522
	calculateImageSelectOptions: function( attachment, controller ) {
2523
2524
		'use strict';
2525
2526
		var control    = controller.get( 'control' ),
2527
		    flexWidth  = !! parseInt( control.params.flex_width, 10 ),
2528
		    flexHeight = !! parseInt( control.params.flex_height, 10 ),
2529
		    realWidth  = attachment.get( 'width' ),
2530
		    realHeight = attachment.get( 'height' ),
2531
		    xInit      = parseInt( control.params.width, 10 ),
2532
		    yInit      = parseInt( control.params.height, 10 ),
2533
		    ratio      = xInit / yInit,
2534
		    xImg       = realWidth,
2535
		    yImg       = realHeight,
2536
		    x1,
2537
		    y1,
2538
		    imgSelectOptions;
2539
2540
		controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
2541
2542
		if ( xImg / yImg > ratio ) {
2543
			yInit = yImg;
2544
			xInit = yInit * ratio;
2545
		} else {
2546
			xInit = xImg;
2547
			yInit = xInit / ratio;
2548
		}
2549
2550
		x1 = ( xImg - xInit ) / 2;
2551
		y1 = ( yImg - yInit ) / 2;
2552
2553
		imgSelectOptions = {
2554
			handles:     true,
2555
			keys:        true,
2556
			instance:    true,
2557
			persistent:  true,
2558
			imageWidth:  realWidth,
2559
			imageHeight: realHeight,
2560
			x1:          x1,
2561
			y1:          y1,
2562
			x2:          xInit + x1,
2563
			y2:          yInit + y1
2564
		};
2565
2566
		if ( false === flexHeight && false === flexWidth ) {
2567
			imgSelectOptions.aspectRatio = xInit + ':' + yInit;
2568
		}
2569
		if ( false === flexHeight ) {
2570
			imgSelectOptions.maxHeight = yInit;
2571
		}
2572
		if ( false === flexWidth ) {
2573
			imgSelectOptions.maxWidth = xInit;
2574
		}
2575
2576
		return imgSelectOptions;
2577
	},
2578
2579
	/**
2580
	 * Return whether the image must be cropped, based on required dimensions.
2581
	 *
2582
	 * @param {bool} flexW
2583
	 * @param {bool} flexH
2584
	 * @param {int}  dstW
2585
	 * @param {int}  dstH
2586
	 * @param {int}  imgW
2587
	 * @param {int}  imgH
2588
	 * @return {bool}
2589
	 */
2590
	mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) {
2591
2592
		'use strict';
2593
2594
		if ( ( true === flexW && true === flexH ) || ( true === flexW && dstH === imgH ) || ( true === flexH && dstW === imgW ) || ( dstW === imgW && dstH === imgH ) || ( imgW <= dstW ) ) {
2595
			return false;
2596
		}
2597
2598
		return true;
2599
	},
2600
2601
	/**
2602
	 * If cropping was skipped, apply the image data directly to the setting.
2603
	 */
2604
	onSkippedCrop: function() {
2605
2606
		'use strict';
2607
2608
		var attachment = this.frame.state().get( 'selection' ).first().toJSON();
2609
		this.setImageInRepeaterField( attachment );
2610
2611
	},
2612
2613
	/**
2614
	 * Updates the setting and re-renders the control UI.
2615
	 *
2616
	 * @param {object} attachment
2617
	 */
2618
	setImageInRepeaterField: function( attachment ) {
2619
2620
		'use strict';
2621
2622
		var $targetDiv = this.$thisButton.closest( '.repeater-field-image,.repeater-field-cropped_image' );
2623
2624
		$targetDiv.find( '.kirki-image-attachment' ).html( '<img src="' + attachment.url + '">' ).hide().slideDown( 'slow' );
2625
2626
		$targetDiv.find( '.hidden-field' ).val( attachment.id );
2627
		this.$thisButton.text( this.$thisButton.data( 'alt-label' ) );
2628
		$targetDiv.find( '.remove-button' ).show();
2629
2630
		//This will activate the save button
2631
		$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
2632
		this.frame.close();
2633
2634
	},
2635
2636
	/**
2637
	 * Updates the setting and re-renders the control UI.
2638
	 *
2639
	 * @param {object} attachment
2640
	 */
2641
	setFileInRepeaterField: function( attachment ) {
2642
2643
		'use strict';
2644
2645
		var $targetDiv = this.$thisButton.closest( '.repeater-field-upload' );
2646
2647
		$targetDiv.find( '.kirki-file-attachment' ).html( '<span class="file"><span class="dashicons dashicons-media-default"></span> ' + attachment.filename + '</span>' ).hide().slideDown( 'slow' );
2648
2649
		$targetDiv.find( '.hidden-field' ).val( attachment.id );
2650
		this.$thisButton.text( this.$thisButton.data( 'alt-label' ) );
2651
		$targetDiv.find( '.upload-button' ).show();
2652
		$targetDiv.find( '.remove-button' ).show();
2653
2654
		//This will activate the save button
2655
		$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
2656
		this.frame.close();
2657
2658
	},
2659
2660
	getMimeType: function() {
2661
2662
		'use strict';
2663
2664
		// We get the field id from which this was called
2665
		var currentFieldId = this.$thisButton.siblings( 'input.hidden-field' ).attr( 'data-field' );
2666
2667
		// Make sure we got it
2668
		if ( _.isString( currentFieldId ) && '' !== currentFieldId ) {
2669
2670
			// Make fields is defined and only do the hack for cropped_image
2671
			if ( _.isObject( this.params.fields[ currentFieldId ] ) && 'upload' === this.params.fields[ currentFieldId ].type ) {
2672
2673
				// If the attribute exists in the field
2674
				if ( ! _.isUndefined( this.params.fields[ currentFieldId ].mime_type ) ) {
2675
2676
					// Set the attribute in the main object
2677
					return this.params.fields[ currentFieldId ].mime_type;
2678
				}
2679
			}
2680
		}
2681
		return 'image';
2682
2683
	},
2684
2685
	removeImage: function( event ) {
2686
2687
		'use strict';
2688
2689
		var $targetDiv,
2690
		    $uploadButton;
2691
2692
		if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
2693
			return;
2694
		}
2695
2696
		$targetDiv = this.$thisButton.closest( '.repeater-field-image,.repeater-field-cropped_image,.repeater-field-upload' );
2697
		$uploadButton = $targetDiv.find( '.upload-button' );
2698
2699
		$targetDiv.find( '.kirki-image-attachment' ).slideUp( 'fast', function() {
2700
			jQuery( this ).show().html( jQuery( this ).data( 'placeholder' ) );
2701
		});
2702
		$targetDiv.find( '.hidden-field' ).val( '' );
2703
		$uploadButton.text( $uploadButton.data( 'label' ) );
2704
		this.$thisButton.hide();
2705
2706
		$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
2707
2708
	},
2709
2710
	removeFile: function( event ) {
2711
2712
		'use strict';
2713
2714
		var $targetDiv,
2715
		    $uploadButton;
2716
2717
		if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
2718
			return;
2719
		}
2720
2721
		$targetDiv = this.$thisButton.closest( '.repeater-field-upload' );
2722
		$uploadButton = $targetDiv.find( '.upload-button' );
2723
2724
		$targetDiv.find( '.kirki-file-attachment' ).slideUp( 'fast', function() {
2725
			jQuery( this ).show().html( jQuery( this ).data( 'placeholder' ) );
2726
		});
2727
		$targetDiv.find( '.hidden-field' ).val( '' );
2728
		$uploadButton.text( $uploadButton.data( 'label' ) );
2729
		this.$thisButton.hide();
2730
2731
		$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
2732
2733
	},
2734
2735
	/**
2736
	 * Get the current value of the setting
2737
	 *
2738
	 * @return Object
2739
	 */
2740
	getValue: function() {
2741
2742
		'use strict';
2743
2744
		// The setting is saved in JSON
2745
		return JSON.parse( decodeURI( this.setting.get() ) );
2746
2747
	},
2748
2749
	/**
2750
	 * Set a new value for the setting
2751
	 *
2752
	 * @param newValue Object
2753
	 * @param refresh If we want to refresh the previewer or not
2754
	 */
2755
	setValue: function( newValue, refresh, filtering ) {
2756
2757
		'use strict';
2758
2759
		// We need to filter the values after the first load to remove data requrired for diplay but that we don't want to save in DB
2760
		var filteredValue = newValue,
2761
		    filter        = [];
2762
2763
		if ( filtering ) {
2764
			jQuery.each( this.params.fields, function( index, value ) {
2765
				if ( 'image' === value.type || 'cropped_image' === value.type || 'upload' === value.type ) {
2766
					filter.push( index );
2767
				}
2768
			});
2769
			jQuery.each( newValue, function( index, value ) {
2770
				jQuery.each( filter, function( ind, field ) {
2771
					if ( ! _.isUndefined( value[ field ] ) && ! _.isUndefined( value[ field ].id ) ) {
2772
						filteredValue[index][ field ] = value[ field ].id;
2773
					}
2774
				});
2775
			});
2776
		}
2777
2778
		this.setting.set( encodeURI( JSON.stringify( filteredValue ) ) );
2779
2780
		if ( refresh ) {
2781
2782
			// Trigger the change event on the hidden field so
2783
			// previewer refresh the website on Customizer
2784
			this.settingField.trigger( 'change' );
2785
		}
2786
	},
2787
2788
	/**
2789
	 * Add a new row to repeater settings based on the structure.
2790
	 *
2791
	 * @param data (Optional) Object of field => value pairs (undefined if you want to get the default values)
2792
	 */
2793
	addRow: function( data ) {
2794
2795
		'use strict';
2796
2797
		var control       = this,
2798
		    template      = control.repeaterTemplate(), // The template for the new row (defined on Kirki_Customize_Repeater_Control::render_content() ).
2799
		    settingValue  = this.getValue(), // Get the current setting value.
2800
		    newRowSetting = {}, // Saves the new setting data.
2801
		    templateData, // Data to pass to the template
2802
		    newRow,
2803
		    i;
2804
2805
		if ( template ) {
2806
2807
			// The control structure is going to define the new fields
2808
			// We need to clone control.params.fields. Assigning it
2809
			// ould result in a reference assignment.
2810
			templateData = jQuery.extend( true, {}, control.params.fields );
2811
2812
			// But if we have passed data, we'll use the data values instead
2813
			if ( data ) {
2814
				for ( i in data ) {
2815
					if ( data.hasOwnProperty( i ) && templateData.hasOwnProperty( i ) ) {
2816
						templateData[ i ]['default'] = data[ i ];
2817
					}
2818
				}
2819
			}
2820
2821
			templateData.index = this.currentIndex;
2822
2823
			// Append the template content
2824
			template = template( templateData );
2825
2826
			// Create a new row object and append the element
2827
			newRow = new RepeaterRow(
2828
				control.currentIndex,
2829
				jQuery( template ).appendTo( control.repeaterFieldsContainer ),
2830
				control.params.row_label,
2831
				control
2832
			);
2833
2834
			newRow.container.on( 'row:remove', function( e, rowIndex ) {
2835
				control.deleteRow( rowIndex );
2836
			});
2837
2838
			newRow.container.on( 'row:update', function( e, rowIndex, fieldName, element ) {
2839
				control.updateField.call( control, e, rowIndex, fieldName, element );
2840
				newRow.updateLabel();
2841
			});
2842
2843
			// Add the row to rows collection
2844
			this.rows[ this.currentIndex ] = newRow;
2845
2846
			for ( i in templateData ) {
2847
				if ( templateData.hasOwnProperty( i ) ) {
2848
					newRowSetting[ i ] = templateData[ i ]['default'];
2849
				}
2850
			}
2851
2852
			settingValue[ this.currentIndex ] = newRowSetting;
2853
			this.setValue( settingValue, true );
2854
2855
			this.currentIndex++;
2856
2857
			return newRow;
2858
		}
2859
	},
2860
2861
	sort: function() {
2862
2863
		'use strict';
2864
2865
		var control     = this,
2866
		    $rows       = this.repeaterFieldsContainer.find( '.repeater-row' ),
2867
		    newOrder    = [],
2868
		    settings    = control.getValue(),
2869
		    newRows     = [],
2870
		    newSettings = [];
2871
2872
		$rows.each( function( i, element ) {
2873
			newOrder.push( jQuery( element ).data( 'row' ) );
2874
		});
2875
2876
		jQuery.each( newOrder, function( newPosition, oldPosition ) {
2877
			newRows[ newPosition ] = control.rows[ oldPosition ];
2878
			newRows[ newPosition ].setRowIndex( newPosition );
2879
2880
			newSettings[ newPosition ] = settings[ oldPosition ];
2881
		});
2882
2883
		control.rows = newRows;
2884
		control.setValue( newSettings );
2885
2886
	},
2887
2888
	/**
2889
	 * Delete a row in the repeater setting
2890
	 *
2891
	 * @param index Position of the row in the complete Setting Array
2892
	 */
2893
	deleteRow: function( index ) {
2894
2895
		'use strict';
2896
2897
		var currentSettings = this.getValue(),
2898
		    row,
2899
		    i,
2900
		    prop;
2901
2902
		if ( currentSettings[ index ] ) {
2903
2904
			// Find the row
2905
			row = this.rows[ index ];
2906
			if ( row ) {
2907
2908
				// Remove the row settings
2909
				delete currentSettings[ index ];
2910
2911
				// Remove the row from the rows collection
2912
				delete this.rows[ index ];
2913
2914
				// Update the new setting values
2915
				this.setValue( currentSettings, true );
2916
2917
			}
2918
2919
		}
2920
2921
		// Remap the row numbers
2922
		i = 1;
2923
		for ( prop in this.rows ) {
2924
			if ( this.rows.hasOwnProperty( prop ) && this.rows[ prop ] ) {
2925
				this.rows[ prop ].updateLabel();
2926
				i++;
2927
			}
2928
		}
2929
	},
2930
2931
	/**
2932
	 * Update a single field inside a row.
2933
	 * Triggered when a field has changed
2934
	 *
2935
	 * @param e Event Object
2936
	 */
2937
	updateField: function( e, rowIndex, fieldId, element ) {
2938
2939
		'use strict';
2940
2941
		var type,
2942
		    row,
2943
		    currentSettings;
2944
2945
		if ( ! this.rows[ rowIndex ] ) {
2946
			return;
2947
		}
2948
2949
		if ( ! this.params.fields[ fieldId ] ) {
2950
			return;
2951
		}
2952
2953
		type            = this.params.fields[ fieldId].type;
2954
		row             = this.rows[ rowIndex ];
2955
		currentSettings = this.getValue();
2956
2957
		element = jQuery( element );
2958
2959
		if ( _.isUndefined( currentSettings[ row.rowIndex ][ fieldId ] ) ) {
2960
			return;
2961
		}
2962
2963
		if ( 'checkbox' === type ) {
2964
			currentSettings[ row.rowIndex ][ fieldId ] = element.is( ':checked' );
2965
		} else {
2966
2967
			// Update the settings
2968
			currentSettings[ row.rowIndex ][ fieldId ] = element.val();
2969
		}
2970
		this.setValue( currentSettings, true );
2971
	},
2972
2973
	/**
2974
	 * Init the color picker on color fields
2975
	 * Called after AddRow
2976
	 *
2977
	 */
2978
	initColorPicker: function() {
2979
2980
		'use strict';
2981
2982
		var control     = this,
2983
		    colorPicker = control.container.find( '.color-picker-hex' ),
2984
		    options     = {},
2985
		    fieldId     = colorPicker.data( 'field' );
2986
2987
		// We check if the color palette parameter is defined.
2988
		if ( ! _.isUndefined( fieldId ) && ! _.isUndefined( control.params.fields[ fieldId ] ) && ! _.isUndefined( control.params.fields[ fieldId ].palettes ) && _.isObject( control.params.fields[ fieldId ].palettes ) ) {
2989
			options.palettes = control.params.fields[ fieldId ].palettes;
2990
		}
2991
2992
		// When the color picker value is changed we update the value of the field
2993
		options.change = function( event, ui ) {
2994
2995
			var currentPicker   = jQuery( event.target ),
2996
			    row             = currentPicker.closest( '.repeater-row' ),
2997
			    rowIndex        = row.data( 'row' ),
2998
			    currentSettings = control.getValue();
2999
3000
			currentSettings[ rowIndex ][ currentPicker.data( 'field' ) ] = ui.color.toString();
3001
			control.setValue( currentSettings, true );
3002
3003
		};
3004
3005
		// Init the color picker
3006
		if ( 0 !== colorPicker.length ) {
3007
			colorPicker.wpColorPicker( options );
3008
		}
3009
	},
3010
3011
	/**
3012
	 * Init the dropdown-pages field with selectWoo
3013
	 * Called after AddRow
3014
	 *
3015
	 * @param {object} theNewRow the row that was added to the repeater
3016
	 * @param {object} data the data for the row if we're initializing a pre-existing row
3017
	 *
3018
	 */
3019
	initSelect: function( theNewRow, data ) {
3020
3021
		'use strict';
3022
3023
		var control  = this,
3024
		    dropdown = theNewRow.container.find( '.repeater-field select' ),
3025
		    $select,
3026
		    dataField,
3027
		    multiple,
3028
		    selectWooOptions = {};
3029
3030
		if ( 0 === dropdown.length ) {
3031
			return;
3032
		}
3033
3034
		dataField = dropdown.data( 'field' );
3035
		multiple  = jQuery( dropdown ).data( 'multiple' );
3036
		if ( 'undefed' !== multiple && jQuery.isNumeric( multiple ) ) {
3037
			multiple = parseInt( multiple, 10 );
3038
			if ( 1 < multiple ) {
3039
				selectWooOptions.maximumSelectionLength = multiple;
3040
			}
3041
		}
3042
3043
		data = data || {};
3044
		data[ dataField ] = data[ dataField ] || '';
3045
3046
		$select = jQuery( dropdown ).selectWoo( selectWooOptions ).val( data[ dataField ] );
3047
3048
		this.container.on( 'change', '.repeater-field select', function( event ) {
3049
3050
			var currentDropdown = jQuery( event.target ),
3051
			    row             = currentDropdown.closest( '.repeater-row' ),
3052
			    rowIndex        = row.data( 'row' ),
3053
			    currentSettings = control.getValue();
3054
3055
			currentSettings[ rowIndex ][ currentDropdown.data( 'field' ) ] = jQuery( this ).val();
3056
			control.setValue( currentSettings );
3057
		});
3058
	}
3059
});
3060
wp.customize.controlConstructor['kirki-slider'] = wp.customize.kirkiDynamicControl.extend({
3061
3062
	initKirkiControl: function() {
3063
		var control      = this,
3064
		    changeAction = ( 'postMessage' === control.setting.transport ) ? 'mousemove change' : 'change',
3065
			rangeInput   = control.container.find( 'input[type="range"]' ),
3066
			textInput    = control.container.find( 'input[type="text"]' ),
3067
		    value        = control.setting._value;
3068
3069
		// Set the initial value in the text input.
3070
		textInput.attr( 'value', value );
3071
3072
		// If the range input value changes copy the value to the text input.
3073
		rangeInput.on( 'mousemove change', function() {
3074
			textInput.attr( 'value', rangeInput.val() );
3075
		} );
3076
3077
		// Save the value when the range input value changes.
3078
		// This is separate from the above because of the postMessage differences.
3079
		// If the control refreshes the preview pane,
3080
		// we don't want a refresh for every change
3081
		// but 1 final refresh when the value is changed.
3082
		rangeInput.on( changeAction, function() {
3083
			control.setting.set( rangeInput.val() );
3084
		} );
3085
3086
		// If the text input value changes,
3087
		// copy the value to the range input
3088
		// and then save.
3089
		textInput.on( 'input paste change', function() {
3090
			rangeInput.attr( 'value', textInput.val() );
3091
			control.setting.set( textInput.val() );
3092
		} );
3093
3094
		// If the reset button is clicked,
3095
		// set slider and text input values to default
3096
		// and hen save.
3097
		control.container.find( '.slider-reset' ).on( 'click', function() {
3098
			textInput.attr( 'value', control.params['default'] );
3099
			rangeInput.attr( 'value', control.params['default'] );
3100
			control.setting.set( textInput.val() );
3101
		} );
3102
	}
3103
});
3104
/* global kirkiControlLoader */
3105
wp.customize.controlConstructor['kirki-sortable'] = wp.customize.Control.extend({
3106
3107
	// When we're finished loading continue processing
3108
	ready: function() {
3109
3110
		'use strict';
3111
3112
		var control = this;
3113
3114
		// Init the control.
3115
		if ( ! _.isUndefined( window.kirkiControlLoader ) && _.isFunction( kirkiControlLoader ) ) {
3116
			kirkiControlLoader( control );
3117
		} else {
3118
			control.initKirkiControl();
3119
		}
3120
	},
3121
3122
	initKirkiControl: function() {
3123
3124
		'use strict';
3125
3126
		var control = this;
3127
3128
		control.container.find( '.kirki-controls-loading-spinner' ).hide();
3129
3130
		// Set the sortable container.
3131
		control.sortableContainer = control.container.find( 'ul.sortable' ).first();
3132
3133
		// Init sortable.
3134
		control.sortableContainer.sortable({
3135
3136
			// Update value when we stop sorting.
3137
			stop: function() {
3138
				control.updateValue();
3139
			}
3140
		}).disableSelection().find( 'li' ).each( function() {
3141
3142
			// Enable/disable options when we click on the eye of Thundera.
3143
			jQuery( this ).find( 'i.visibility' ).click( function() {
3144
				jQuery( this ).toggleClass( 'dashicons-visibility-faint' ).parents( 'li:eq(0)' ).toggleClass( 'invisible' );
3145
			});
3146
		}).click( function() {
3147
3148
			// Update value on click.
3149
			control.updateValue();
3150
		});
3151
	},
3152
3153
	/**
3154
	 * Updates the sorting list
3155
	 */
3156
	updateValue: function() {
3157
3158
		'use strict';
3159
3160
		var control = this,
3161
		    newValue = [];
3162
3163
		this.sortableContainer.find( 'li' ).each( function() {
3164
			if ( ! jQuery( this ).is( '.invisible' ) ) {
3165
				newValue.push( jQuery( this ).data( 'value' ) );
3166
			}
3167
		});
3168
		control.setting.set( newValue );
3169
	}
3170
});
3171
wp.customize.controlConstructor['kirki-switch'] = wp.customize.kirkiDynamicControl.extend({
3172
3173
	initKirkiControl: function() {
3174
3175
		'use strict';
3176
3177
		var control       = this,
3178
		    checkboxValue = control.setting._value;
3179
3180
		// Save the value
3181
		this.container.on( 'change', 'input', function() {
3182
			checkboxValue = ( jQuery( this ).is( ':checked' ) ) ? true : false;
3183
			control.setting.set( checkboxValue );
3184
		});
3185
	}
3186
});
3187
wp.customize.controlConstructor['kirki-toggle'] = wp.customize.kirkiDynamicControl.extend({
3188
3189
	initKirkiControl: function() {
3190
3191
		var control = this,
3192
		    checkboxValue = control.setting._value;
3193
3194
		// Save the value
3195
		this.container.on( 'change', 'input', function() {
3196
			checkboxValue = ( jQuery( this ).is( ':checked' ) ) ? true : false;
3197
			control.setting.set( checkboxValue );
3198
		});
3199
	}
3200
});
3201
/* global kirkiL10n, kirki */
3202
wp.customize.controlConstructor['kirki-typography'] = wp.customize.kirkiDynamicControl.extend({
3203
3204
	initKirkiControl: function() {
3205
3206
		'use strict';
3207
3208
		var control = this,
3209
		    value   = control.setting._value,
3210
		    picker;
3211
3212
		control.renderFontSelector();
3213
		control.renderBackupFontSelector();
3214
		control.renderVariantSelector();
3215
		control.renderSubsetSelector();
3216
3217
		// Font-size.
3218
		if ( control.params['default']['font-size'] ) {
3219
			this.container.on( 'change keyup paste', '.font-size input', function() {
3220
				control.saveValue( 'font-size', jQuery( this ).val() );
3221
			});
3222
		}
3223
3224
		// Line-height.
3225
		if ( control.params['default']['line-height'] ) {
3226
			this.container.on( 'change keyup paste', '.line-height input', function() {
3227
				control.saveValue( 'line-height', jQuery( this ).val() );
3228
			});
3229
		}
3230
3231
		// Margin-top.
3232
		if ( control.params['default']['margin-top'] ) {
3233
			this.container.on( 'change keyup paste', '.margin-top input', function() {
3234
				control.saveValue( 'margin-top', jQuery( this ).val() );
3235
			});
3236
		}
3237
3238
		// Margin-bottom.
3239
		if ( control.params['default']['margin-bottom'] ) {
3240
			this.container.on( 'change keyup paste', '.margin-bottom input', function() {
3241
				control.saveValue( 'margin-bottom', jQuery( this ).val() );
3242
			});
3243
		}
3244
3245
		// Letter-spacing.
3246
		if ( control.params['default']['letter-spacing'] ) {
3247
			value['letter-spacing'] = ( jQuery.isNumeric( value['letter-spacing'] ) ) ? value['letter-spacing'] + 'px' : value['letter-spacing'];
3248
			this.container.on( 'change keyup paste', '.letter-spacing input', function() {
3249
				value['letter-spacing'] = ( jQuery.isNumeric( jQuery( this ).val() ) ) ? jQuery( this ).val() + 'px' : jQuery( this ).val();
3250
				control.saveValue( 'letter-spacing', value['letter-spacing'] );
3251
			});
3252
		}
3253
3254
		// Word-spacing.
3255
		if ( control.params['default']['word-spacing'] ) {
3256
			this.container.on( 'change keyup paste', '.word-spacing input', function() {
3257
				control.saveValue( 'word-spacing', jQuery( this ).val() );
3258
			});
3259
		}
3260
3261
		// Text-align.
3262
		if ( control.params['default']['text-align'] ) {
3263
			this.container.on( 'change', '.text-align input', function() {
3264
				control.saveValue( 'text-align', jQuery( this ).val() );
3265
			});
3266
		}
3267
3268
		// Text-transform.
3269
		if ( control.params['default']['text-transform'] ) {
3270
			jQuery( control.selector + ' .text-transform select' ).selectWoo().on( 'change', function() {
3271
				control.saveValue( 'text-transform', jQuery( this ).val() );
3272
			});
3273
		}
3274
3275
		// Text-decoration.
3276
		if ( control.params['default']['text-decoration'] ) {
3277
			jQuery( control.selector + ' .text-decoration select' ).selectWoo().on( 'change', function() {
3278
				control.saveValue( 'text-decoration', jQuery( this ).val() );
3279
			});
3280
		}
3281
3282
		// Color.
3283
		if ( control.params['default'].color ) {
3284
			picker = this.container.find( '.kirki-color-control' );
3285
			picker.wpColorPicker({
3286
				change: function() {
3287
					setTimeout( function() {
3288
						control.saveValue( 'color', picker.val() );
3289
					}, 100 );
3290
				}
3291
			});
3292
		}
3293
	},
3294
3295
	/**
3296
	 * Adds the font-families to the font-family dropdown
3297
	 * and instantiates selectWoo.
3298
	 */
3299
	renderFontSelector: function() {
3300
3301
		var control         = this,
3302
		    selector        = control.selector + ' .font-family select',
3303
		    data            = [],
3304
		    standardFonts   = [],
3305
		    googleFonts     = [],
3306
		    value           = control.setting._value,
3307
		    fonts           = control.getFonts(),
3308
		    fontSelect;
3309
3310
		// Format standard fonts as an array.
3311
		if ( ! _.isUndefined( fonts.standard ) ) {
3312
			_.each( fonts.standard, function( font ) {
3313
				standardFonts.push({
3314
					id: font.family.replace( /&quot;/g, '&#39' ),
3315
					text: font.label
3316
				});
3317
			});
3318
		}
3319
3320
		// Format google fonts as an array.
3321
		if ( ! _.isUndefined( fonts.google ) ) {
3322
			_.each( fonts.google, function( font ) {
3323
				googleFonts.push({
3324
					id: font.family,
3325
					text: font.family
3326
				});
3327
			});
3328
		}
3329
3330
		// Combine forces and build the final data.
3331
		data = [
3332
			{ text: kirkiL10n.standardFonts, children: standardFonts },
3333
			{ text: kirkiL10n.googleFonts, children: googleFonts }
3334
		];
3335
3336
		// Instantiate selectWoo with the data.
3337
		fontSelect = jQuery( selector ).selectWoo({
3338
			data: data
3339
		});
3340
3341
		// Set the initial value.
3342
		if ( value['font-family'] ) {
3343
			fontSelect.val( value['font-family'].replace( /'/g, '"' ) ).trigger( 'change' );
3344
		}
3345
3346
		// When the value changes
3347
		fontSelect.on( 'change', function() {
3348
3349
			// Set the value.
3350
			control.saveValue( 'font-family', jQuery( this ).val() );
3351
3352
			// Re-init the font-backup selector.
3353
			control.renderBackupFontSelector();
3354
3355
			// Re-init variants selector.
3356
			control.renderVariantSelector();
3357
3358
			// Re-init subsets selector.
3359
			control.renderSubsetSelector();
3360
		});
3361
	},
3362
3363
	/**
3364
	 * Adds the font-families to the font-family dropdown
3365
	 * and instantiates selectWoo.
3366
	 */
3367
	renderBackupFontSelector: function() {
3368
3369
		var control       = this,
3370
		    selector      = control.selector + ' .font-backup select',
3371
		    standardFonts = [],
3372
		    value         = control.setting._value,
3373
		    fontFamily    = value['font-family'],
3374
		    fonts         = control.getFonts(),
3375
		    fontSelect;
3376
3377
		if ( _.isUndefined( value['font-backup'] ) || null === value['font-backup'] ) {
3378
			value['font-backup'] = '';
3379
		}
3380
3381
		// Hide if we're not on a google-font.
3382
		if ( 'google' !== kirki.util.webfonts.getFontType( fontFamily ) ) {
3383
			jQuery( control.selector + ' .font-backup' ).hide();
3384
			return;
3385
		}
3386
		jQuery( control.selector + ' .font-backup' ).show();
3387
3388
		// Format standard fonts as an array.
3389
		if ( ! _.isUndefined( fonts.standard ) ) {
3390
			_.each( fonts.standard, function( font ) {
3391
				standardFonts.push({
3392
					id: font.family.replace( /&quot;/g, '&#39' ),
3393
					text: font.label
3394
				});
3395
			});
3396
		}
3397
3398
		// Instantiate selectWoo with the data.
3399
		fontSelect = jQuery( selector ).selectWoo({
3400
			data: standardFonts
3401
		});
3402
3403
		// Set the initial value.
3404
		if ( 'undefined' !== typeof value['font-backup'] ) {
3405
			fontSelect.val( value['font-backup'].replace( /'/g, '"' ) ).trigger( 'change' );
3406
		}
3407
3408
		// When the value changes
3409
		fontSelect.on( 'change', function() {
3410
3411
			// Set the value.
3412
			control.saveValue( 'font-backup', jQuery( this ).val() );
3413
		});
3414
	},
3415
3416
	/**
3417
	 * Renders the variants selector using selectWoo
3418
	 * Displays font-variants for the currently selected font-family.
3419
	 */
3420
	renderVariantSelector: function() {
3421
3422
		var control    = this,
3423
		    value      = control.setting._value,
3424
		    fontFamily = value['font-family'],
3425
		    selector   = control.selector + ' .variant select',
3426
		    data       = [],
3427
		    isValid    = false,
3428
		    fontType   = kirki.util.webfonts.getFontType( fontFamily ),
3429
		    variants   = ['regular', 'italic', '700', '700italic'],
3430
		    fontWeight,
3431
		    variantSelector,
3432
		    fontStyle;
3433
3434
		if ( 'google' === fontType ) {
3435
			variants = kirki.util.webfonts.google.getVariants( fontFamily );
3436
		}
3437
3438
		jQuery( control.selector + ' .variant' ).show();
3439
		_.each( variants, function( variant ) {
3440
			if ( value.variant === variant ) {
3441
				isValid = true;
3442
			}
3443
			data.push({
3444
				id: variant,
3445
				text: variant
3446
			});
3447
		});
3448
		if ( ! isValid ) {
3449
			value.variant = 'regular';
3450
		}
3451
3452
		if ( jQuery( selector ).hasClass( 'select2-hidden-accessible' ) ) {
3453
			jQuery( selector ).selectWoo( 'destroy' );
3454
			jQuery( selector ).empty();
3455
		}
3456
3457
		// Instantiate selectWoo with the data.
3458
		variantSelector = jQuery( selector ).selectWoo({
3459
			data: data
3460
		});
3461
		variantSelector.val( value.variant ).trigger( 'change' );
3462
		variantSelector.on( 'change', function() {
3463
			control.saveValue( 'variant', jQuery( this ).val() );
3464
3465
			fontWeight = ( ! _.isString( value.variant ) ) ? '400' : value.variant.match( /\d/g );
3466
			fontWeight = ( ! _.isObject( fontWeight ) ) ? '400' : fontWeight.join( '' );
3467
			fontStyle  = ( -1 !== value.variant.indexOf( 'italic' ) ) ? 'italic' : 'normal';
3468
3469
			control.saveValue( 'font-weight', fontWeight );
3470
			control.saveValue( 'font-style', fontStyle );
3471
		});
3472
	},
3473
3474
	/**
3475
	 * Renders the subsets selector using selectWoo
3476
	 * Displays font-subsets for the currently selected font-family.
3477
	 */
3478
	renderSubsetSelector: function() {
3479
3480
		var control    = this,
3481
		    value      = control.setting._value,
3482
		    fontFamily = value['font-family'],
3483
		    subsets    = kirki.util.webfonts.google.getSubsets( fontFamily ),
3484
		    selector   = control.selector + ' .subsets select',
3485
		    data       = [],
3486
		    validValue = value.subsets,
3487
		    subsetSelector;
3488
3489
		if ( false !== subsets ) {
3490
			jQuery( control.selector + ' .subsets' ).show();
3491
			_.each( subsets, function( subset ) {
3492
3493
				if ( _.isObject( validValue ) ) {
3494
					if ( -1 === validValue.indexOf( subset ) ) {
3495
						validValue = _.reject( validValue, function( subValue ) {
3496
							return subValue === subset;
3497
						});
3498
					}
3499
				}
3500
3501
				data.push({
3502
					id: subset,
3503
					text: subset
3504
				});
3505
			});
3506
3507
		} else {
3508
			jQuery( control.selector + ' .subsets' ).hide();
3509
		}
3510
3511
		if ( jQuery( selector ).hasClass( 'select2-hidden-accessible' ) ) {
3512
			jQuery( selector ).selectWoo( 'destroy' );
3513
			jQuery( selector ).empty();
3514
		}
3515
3516
		// Instantiate selectWoo with the data.
3517
		subsetSelector = jQuery( selector ).selectWoo({
3518
			data: data
3519
		});
3520
		subsetSelector.val( validValue ).trigger( 'change' );
3521
		subsetSelector.on( 'change', function() {
3522
			control.saveValue( 'subsets', jQuery( this ).val() );
3523
		});
3524
	},
3525
3526
	/**
3527
	 * Get fonts.
3528
	 */
3529
	getFonts: function() {
3530
		var control            = this,
3531
		    initialGoogleFonts = kirki.util.webfonts.google.getFonts(),
3532
		    googleFonts        = {},
3533
		    googleFontsSort    = 'alpha',
3534
			googleFontsNumber  = 0,
3535
		    standardFonts      = {};
3536
3537
		// Get google fonts.
3538
		if ( ! _.isEmpty( control.params.choices.fonts.google ) ) {
3539
			if ( 'alpha' === control.params.choices.fonts.google[0] || 'popularity' === control.params.choices.fonts.google[0] || 'trending' === control.params.choices.fonts.google[0] ) {
3540
				googleFontsSort = control.params.choices.fonts.google[0];
3541
				if ( ! isNaN( control.params.choices.fonts.google[1] ) ) {
3542
					googleFontsNumber = parseInt( control.params.choices.fonts.google[1], 10 );
3543
				}
3544
				googleFonts = kirki.util.webfonts.google.getFonts( googleFontsSort, googleFontsNumber );
3545
3546
			} else {
3547
				_.each( control.params.choices.fonts.google, function( fontName ) {
3548
					if ( 'undefined' !== typeof initialGoogleFonts[ fontName ] && ! _.isEmpty( initialGoogleFonts[ fontName ] ) ) {
3549
						googleFonts[ fontName ] = initialGoogleFonts[ fontName ];
3550
					}
3551
				} );
3552
			}
3553
		} else {
3554
			googleFonts = kirki.util.webfonts.google.getFonts( googleFontsSort, googleFontsNumber );
3555
		}
3556
3557
		// Get standard fonts.
3558
		if ( ! _.isEmpty( control.params.choices.fonts.standard ) ) {
3559
			_.each( control.params.choices.fonts.standard, function( fontName ) {
3560
				if ( 'undefined' !== typeof kirki.util.webfonts.standard.fonts[ fontName ] && ! _.isEmpty( kirki.util.webfonts.standard.fonts[ fontName ] ) ) {
3561
					standardFonts[ fontName ] = {};
3562
					if ( 'undefined' !== kirki.util.webfonts.standard.fonts[ fontName ].stack && ! _.isEmpty( kirki.util.webfonts.standard.fonts[ fontName ].stack ) ) {
3563
						standardFonts[ fontName ].family = kirki.util.webfonts.standard.fonts[ fontName ].stack;
3564
					} else {
3565
						standardFonts[ fontName ].family = googleFonts[ fontName ];
3566
					}
3567
					if ( 'undefined' !== kirki.util.webfonts.standard.fonts[ fontName ].label && ! _.isEmpty( kirki.util.webfonts.standard.fonts[ fontName ].label ) ) {
3568
						standardFonts[ fontName ].label = kirki.util.webfonts.standard.fonts[ fontName ].label;
3569
					} else if ( ! _.isEmpty( standardFonts[ fontName ] ) ) {
3570
						standardFonts[ fontName ].label = standardFonts[ fontName ];
3571
					}
3572
				} else {
3573
					standardFonts[ fontName ] = {
3574
						family: fontName,
3575
						label: fontName
3576
					};
3577
				}
3578
			} );
3579
		} else {
3580
			_.each( kirki.util.webfonts.standard.fonts, function( font, id ) {
3581
				standardFonts[ id ] = {
3582
					family: font.stack,
3583
					label: font.label
3584
				};
3585
			} );
3586
		}
3587
		return {
3588
			google: googleFonts,
3589
			standard: standardFonts
3590
		};
3591
	},
3592
3593
	/**
3594
	 * Saves the value.
3595
	 */
3596
	saveValue: function( property, value ) {
3597
3598
		var control = this,
3599
		    input   = control.container.find( '.typography-hidden-value' ),
3600
		    val     = control.setting._value;
3601
3602
		val[ property ] = value;
3603
3604
		jQuery( input ).attr( 'value', JSON.stringify( val ) ).trigger( 'change' );
3605
		control.setting.set( val );
3606
	}
3607
});
3608